Initial commit - CMS Gov Bapenda Garut dengan EditorJS
This commit is contained in:
203
app/Views/admin/audit-logs/index.php
Normal file
203
app/Views/admin/audit-logs/index.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?= $this->extend('admin/layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-5 sm:space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white/90">
|
||||
Audit Log
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Riwayat aktivitas sistem
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Card -->
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-1">
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Total Log</p>
|
||||
<p class="text-2xl font-bold text-gray-800 dark:text-white"><?= number_format($total) ?></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-full bg-brand-100 dark:bg-brand-900/20 flex items-center justify-center">
|
||||
<i class="fe fe-clipboard text-brand-600 dark:text-brand-400 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters and Search -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<form method="get" action="<?= base_url('admin/audit-logs') ?>" class="flex flex-col gap-4 sm:flex-row sm:items-center">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
value="<?= esc($search ?? '') ?>"
|
||||
placeholder="Cari aksi, user, atau IP address..."
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
name="action"
|
||||
class="h-11 rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
>
|
||||
<option value="">Semua Aksi</option>
|
||||
<?php foreach ($actions as $action): ?>
|
||||
<option value="<?= esc($action['action']) ?>" <?= ($actionFilter === $action['action']) ? 'selected' : '' ?>>
|
||||
<?= esc($action['action']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
name="user"
|
||||
class="h-11 rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
>
|
||||
<option value="">Semua User</option>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<option value="<?= esc($user['id']) ?>" <?= ($userFilter == $user['id']) ? 'selected' : '' ?>>
|
||||
<?= esc($user['username']) ?> (<?= esc($user['email']) ?>)
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-search"></i>
|
||||
Cari
|
||||
</button>
|
||||
<?php if (!empty($search) || !empty($actionFilter) || !empty($userFilter)): ?>
|
||||
<a
|
||||
href="<?= base_url('admin/audit-logs') ?>"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
<i class="fe fe-x"></i>
|
||||
Reset
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Audit Logs Table -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="max-w-full overflow-x-auto">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-100 dark:border-gray-800">
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Waktu
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
User
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Aksi
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
IP Address
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
User Agent
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<?php if (empty($auditLogs)): ?>
|
||||
<tr>
|
||||
<td colspan="5" class="px-5 py-8 text-center sm:px-6">
|
||||
<p class="text-gray-500 dark:text-gray-400">Tidak ada log ditemukan.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($auditLogs as $log): ?>
|
||||
<tr>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="text-gray-500 text-sm dark:text-gray-400">
|
||||
<?= date('d M Y H:i:s', strtotime($log['created_at'])) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-800 text-sm dark:text-white/90">
|
||||
<?= esc($log['username'] ?? 'System') ?>
|
||||
</p>
|
||||
<?php if (!empty($log['email'])): ?>
|
||||
<p class="ml-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
(<?= esc($log['email']) ?>)
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="rounded-full bg-brand-50 px-2 py-0.5 text-xs font-medium text-brand-700 dark:bg-brand-500/15 dark:text-brand-500">
|
||||
<?= esc($log['action']) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="text-gray-500 text-sm dark:text-gray-400">
|
||||
<?= esc($log['ip_address']) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="text-gray-500 text-xs dark:text-gray-400 max-w-xs truncate" title="<?= esc($log['user_agent']) ?>">
|
||||
<?= esc($log['user_agent']) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<?php if ($pager->hasMore() || $pager->getCurrentPage() > 1): ?>
|
||||
<div class="flex items-center justify-between border-t border-gray-100 px-5 py-4 dark:border-gray-800 sm:px-6">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Menampilkan <?= count($auditLogs) ?> dari <?= $pager->getTotal() ?> log
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<?= $pager->links() ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
179
app/Views/admin/dashboard.php
Normal file
179
app/Views/admin/dashboard.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?= $this->extend('admin/layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-5 sm:space-y-6">
|
||||
<!-- Welcome Card -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-2">
|
||||
Selamat Datang, <?= esc(session()->get('username') ?? 'User') ?>!
|
||||
</h2>
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
Ini adalah dashboard admin Bapenda Garut. Gunakan menu di sidebar untuk navigasi.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-6 lg:grid-cols-4">
|
||||
<!-- Total News Card -->
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-brand-100 dark:bg-brand-900/20">
|
||||
<i class="fe fe-file-text text-brand-600 dark:text-brand-400 text-xl"></i>
|
||||
</div>
|
||||
<div class="mt-5 flex items-end justify-between">
|
||||
<div>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">Total Berita</span>
|
||||
<h4 class="mt-2 text-title-sm font-bold text-gray-800 dark:text-white/90">
|
||||
<?= number_format($stats['news']['total']) ?>
|
||||
</h4>
|
||||
<div class="mt-2 flex gap-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>Published: <?= $stats['news']['published'] ?></span>
|
||||
<span>•</span>
|
||||
<span>Draft: <?= $stats['news']['draft'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Pages Card -->
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-success-100 dark:bg-success-900/20">
|
||||
<i class="fe fe-file text-success-600 dark:text-success-400 text-xl"></i>
|
||||
</div>
|
||||
<div class="mt-5 flex items-end justify-between">
|
||||
<div>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">Total Halaman</span>
|
||||
<h4 class="mt-2 text-title-sm font-bold text-gray-800 dark:text-white/90">
|
||||
<?= number_format($stats['pages']['total']) ?>
|
||||
</h4>
|
||||
<div class="mt-2 flex gap-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>Published: <?= $stats['pages']['published'] ?></span>
|
||||
<span>•</span>
|
||||
<span>Draft: <?= $stats['pages']['draft'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Users Card -->
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-purple-100 dark:bg-purple-900/20">
|
||||
<i class="fe fe-users text-purple-600 dark:text-purple-400 text-xl"></i>
|
||||
</div>
|
||||
<div class="mt-5 flex items-end justify-between">
|
||||
<div>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">Total Pengguna</span>
|
||||
<h4 class="mt-2 text-title-sm font-bold text-gray-800 dark:text-white/90">
|
||||
<?= number_format($stats['users']['total']) ?>
|
||||
</h4>
|
||||
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<span>Aktif: <?= $stats['users']['active'] ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Published News Card -->
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03] md:p-6">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-warning-100 dark:bg-warning-900/20">
|
||||
<i class="fe fe-check-circle text-warning-600 dark:text-warning-400 text-xl"></i>
|
||||
</div>
|
||||
<div class="mt-5 flex items-end justify-between">
|
||||
<div>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">Berita Published</span>
|
||||
<h4 class="mt-2 text-title-sm font-bold text-gray-800 dark:text-white/90">
|
||||
<?= number_format($stats['news']['published']) ?>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity Table -->
|
||||
<div class="rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="px-5 py-4 sm:px-6 sm:py-5">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white/90">
|
||||
Aktivitas Terbaru
|
||||
</h3>
|
||||
</div>
|
||||
<div class="max-w-full overflow-x-auto">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-100 dark:border-gray-800">
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Waktu
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
User
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Aksi
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
IP Address
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<?php if (empty($recentAuditLogs)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="px-5 py-8 text-center sm:px-6">
|
||||
<p class="text-gray-500 dark:text-gray-400">Tidak ada aktivitas terbaru.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($recentAuditLogs as $log): ?>
|
||||
<tr>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="text-gray-500 text-sm dark:text-gray-400">
|
||||
<?= date('d M Y H:i', strtotime($log['created_at'])) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-800 text-sm dark:text-white/90">
|
||||
<?= esc($log['username'] ?? 'System') ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="rounded-full bg-brand-50 px-2 py-0.5 text-xs font-medium text-brand-700 dark:bg-brand-500/15 dark:text-brand-500">
|
||||
<?= esc($log['action']) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="text-gray-500 text-sm dark:text-gray-400">
|
||||
<?= esc($log['ip_address']) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
151
app/Views/admin/layout.php
Normal file
151
app/Views/admin/layout.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<meta name="csrf-token" content="<?= csrf_hash() ?>" />
|
||||
<meta name="csrf-header" content="<?= csrf_header() ?>" />
|
||||
<title><?= esc($title ?? 'Admin Dashboard') ?> - Bapenda Garut</title>
|
||||
<link rel="icon" type="image/png" href="<?= base_url('assets/images/favicon_1762970389090.png') ?>" />
|
||||
<link rel="shortcut icon" type="image/png" href="<?= base_url('assets/images/favicon_1762970389090.png') ?>" />
|
||||
<link rel="stylesheet" href="<?= base_url('assets/css/app.css') ?>">
|
||||
<style>
|
||||
/* Fix Editor.js toolbar z-index to stay below header */
|
||||
.ce-toolbar,
|
||||
.ce-inline-toolbar,
|
||||
.ce-popover,
|
||||
.ce-conversion-toolbar,
|
||||
.ce-settings,
|
||||
.ce-block-settings,
|
||||
.ce-toolbar__plus,
|
||||
.ce-toolbar__settings-btn,
|
||||
.ce-popover__item,
|
||||
.ce-popover__items,
|
||||
.ce-settings__button,
|
||||
.ce-toolbar__content,
|
||||
.ce-toolbar__actions {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
header,
|
||||
header[class*="sticky"],
|
||||
header[class*="fixed"],
|
||||
header.sticky,
|
||||
header.fixed {
|
||||
z-index: 99999 !important;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<script>
|
||||
// Apply dark mode immediately if stored (before Alpine loads)
|
||||
(function() {
|
||||
const darkMode = JSON.parse(localStorage.getItem('darkMode') || 'false');
|
||||
if (darkMode) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
})();
|
||||
|
||||
// Initialize Alpine store for dark mode
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('darkMode', {
|
||||
enabled: JSON.parse(localStorage.getItem('darkMode') || 'false'),
|
||||
toggle() {
|
||||
this.enabled = !this.enabled;
|
||||
localStorage.setItem('darkMode', JSON.stringify(this.enabled));
|
||||
if (this.enabled) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body
|
||||
x-data="{ sidebarToggle: false }"
|
||||
:class="{'bg-gray-900': $store.darkMode.enabled}"
|
||||
>
|
||||
<!-- ===== Page Wrapper Start ===== -->
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
<!-- ===== Sidebar Start ===== -->
|
||||
<?= $this->include('admin/partials/sidebar') ?>
|
||||
<!-- ===== Sidebar End ===== -->
|
||||
|
||||
<!-- ===== Content Area Start ===== -->
|
||||
<div class="relative flex flex-col flex-1 overflow-x-hidden overflow-y-auto">
|
||||
<!-- ===== Header Start ===== -->
|
||||
<?= $this->include('admin/partials/navbar') ?>
|
||||
<!-- ===== Header End ===== -->
|
||||
|
||||
<!-- ===== Main Content Start ===== -->
|
||||
<main>
|
||||
<div class="p-4 mx-auto max-w-7xl md:p-6">
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg">
|
||||
<p class="text-sm text-green-800"><?= esc(session()->getFlashdata('success')) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p class="text-sm text-red-800"><?= esc(session()->getFlashdata('error')) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= $this->renderSection('content') ?>
|
||||
</div>
|
||||
</main>
|
||||
<!-- ===== Main Content End ===== -->
|
||||
</div>
|
||||
<!-- ===== Content Area End ===== -->
|
||||
</div>
|
||||
<!-- ===== Page Wrapper End ===== -->
|
||||
|
||||
<script src="<?= base_url('assets/js/app.js') ?>"></script>
|
||||
<script>
|
||||
// CSRF Helper for AJAX/Fetch requests
|
||||
function withCsrf(options = {}) {
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
const csrfHeader = document.querySelector('meta[name="csrf-header"]')?.getAttribute('content');
|
||||
|
||||
if (!csrfToken || !csrfHeader) {
|
||||
console.warn('CSRF token not found');
|
||||
return options;
|
||||
}
|
||||
|
||||
// Merge headers
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
[csrfHeader]: csrfToken,
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
// Override fetch to automatically include CSRF token
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function(url, options = {}) {
|
||||
// Only add CSRF for same-origin POST/PUT/DELETE requests
|
||||
if (typeof url === 'string' && (url.startsWith('/') || url.startsWith(window.location.origin))) {
|
||||
const method = (options.method || 'GET').toUpperCase();
|
||||
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(method)) {
|
||||
options = withCsrf(options);
|
||||
}
|
||||
}
|
||||
return originalFetch(url, options);
|
||||
};
|
||||
|
||||
// Update CSRF token in meta tags after form submission
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Listen for form submissions and update CSRF token from response
|
||||
document.addEventListener('submit', function(e) {
|
||||
// After form submit, the new CSRF token will be in the response
|
||||
// We'll update it when the page reloads or via AJAX response
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?= $this->renderSection('scripts') ?>
|
||||
</body>
|
||||
</html>
|
||||
170
app/Views/admin/news/form.php
Normal file
170
app/Views/admin/news/form.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?= $this->extend('admin/layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-5 sm:space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white/90">
|
||||
<?= $news ? 'Edit Berita' : 'Tambah Berita' ?>
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<?= $news ? 'Ubah informasi berita' : 'Tambahkan berita baru' ?>
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="<?= base_url('admin/news') ?>"
|
||||
class="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
<i class="fe fe-arrow-left"></i>
|
||||
Kembali
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Flash Messages sudah ditangani di layout.php -->
|
||||
|
||||
<!-- Form -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="p-5 sm:p-6">
|
||||
<form
|
||||
action="<?= $news ? base_url('admin/news/update/' . $news['id']) : base_url('admin/news/store') ?>"
|
||||
method="post"
|
||||
class="space-y-6"
|
||||
>
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<!-- Title -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Judul <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="title"
|
||||
value="<?= old('title', $news['title'] ?? '') ?>"
|
||||
placeholder="Masukkan judul berita"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
/>
|
||||
<?php if (isset($validation) && $validation->hasError('title')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('title')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Editor.js Container -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Konten <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<div id="editorjs" class="min-h-[300px] rounded-lg border border-gray-300 bg-white p-4 dark:border-gray-700 dark:bg-gray-900"></div>
|
||||
|
||||
<!-- Hidden inputs for Editor.js data -->
|
||||
<input type="hidden" name="content" id="content" value="<?= esc($news['content'] ?? '') ?>">
|
||||
<input type="hidden" name="content_json" id="content_json" value="<?= esc($news['content_json'] ?? '') ?>">
|
||||
<input type="hidden" name="content_html" id="content_html" value="<?= esc($news['content_html'] ?? '') ?>">
|
||||
<input type="hidden" name="excerpt" id="excerpt" value="<?= esc($news['excerpt'] ?? '') ?>">
|
||||
|
||||
<?php if (isset($validation) && $validation->hasError('content')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('content')) ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if (isset($validation) && $validation->hasError('content_json')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('content_json')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Status <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
name="status"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
required
|
||||
>
|
||||
<option value="">Pilih Status</option>
|
||||
<option value="draft" <?= old('status', $news['status'] ?? '') === 'draft' ? 'selected' : '' ?>>
|
||||
Draft
|
||||
</option>
|
||||
<option value="published" <?= old('status', $news['status'] ?? '') === 'published' ? 'selected' : '' ?>>
|
||||
Published
|
||||
</option>
|
||||
</select>
|
||||
<?php if (isset($validation) && $validation->hasError('status')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('status')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex items-center gap-3 border-t border-gray-100 pt-6 dark:border-gray-800">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-save"></i>
|
||||
<?= $news ? 'Simpan Perubahan' : 'Simpan Berita' ?>
|
||||
</button>
|
||||
<a
|
||||
href="<?= base_url('admin/news') ?>"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
Batal
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Editor.js Bundle (Built by Vite) -->
|
||||
<?php
|
||||
// Get manifest file to load hashed assets
|
||||
$manifestPath = FCPATH . 'assets/editor/.vite/manifest.json';
|
||||
$editorJsPath = base_url('assets/editor/editor.js'); // Fallback
|
||||
|
||||
if (file_exists($manifestPath)) {
|
||||
$manifest = json_decode(file_get_contents($manifestPath), true);
|
||||
if (isset($manifest['resources/js/editor/editor.js'])) {
|
||||
$editorJsPath = base_url('assets/editor/' . $manifest['resources/js/editor/editor.js']['file']);
|
||||
}
|
||||
}
|
||||
?>
|
||||
<script src="<?= $editorJsPath ?>"></script>
|
||||
<script>
|
||||
// CSRF & Endpoints for Editor.js
|
||||
window.csrfTokenName = '<?= csrf_token() ?>';
|
||||
window.csrfTokenValue = '<?= csrf_hash() ?>';
|
||||
window.csrfHeaderName = '<?= csrf_header() ?>';
|
||||
window.uploadEndpoint = '<?= base_url('admin/upload') ?>';
|
||||
window.linkPreviewEndpoint = '<?= base_url('admin/link-preview') ?>';
|
||||
window.newsId = <?= $news ? $news['id'] : 'null' ?>;
|
||||
|
||||
// Fix Editor.js toolbar z-index to stay below header
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.ce-toolbar,
|
||||
.ce-inline-toolbar,
|
||||
.ce-popover,
|
||||
.ce-conversion-toolbar,
|
||||
.ce-settings,
|
||||
.ce-block-settings,
|
||||
.ce-toolbar__plus,
|
||||
.ce-toolbar__settings-btn,
|
||||
.ce-popover__item,
|
||||
.ce-popover__items,
|
||||
.ce-settings__button {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
header,
|
||||
header[class*="sticky"],
|
||||
header[class*="fixed"] {
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
</script>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
319
app/Views/admin/news/index.php
Normal file
319
app/Views/admin/news/index.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?= $this->extend('admin/layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-5 sm:space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white/90">
|
||||
Berita
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Kelola berita dan artikel
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="<?= base_url('admin/news/create') ?>"
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-plus"></i>
|
||||
Tambah Berita
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Total Berita</p>
|
||||
<p class="text-2xl font-bold text-gray-800 dark:text-white"><?= $stats['total'] ?></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-full bg-brand-100 dark:bg-brand-900/20 flex items-center justify-center">
|
||||
<i class="fe fe-file-text text-brand-600 dark:text-brand-400 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Published</p>
|
||||
<p class="text-2xl font-bold text-gray-800 dark:text-white"><?= $stats['published'] ?></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-full bg-success-100 dark:bg-success-900/20 flex items-center justify-center">
|
||||
<i class="fe fe-check-circle text-success-600 dark:text-success-400 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Draft</p>
|
||||
<p class="text-2xl font-bold text-gray-800 dark:text-white"><?= $stats['draft'] ?></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-full bg-warning-100 dark:bg-warning-900/20 flex items-center justify-center">
|
||||
<i class="fe fe-edit text-warning-600 dark:text-warning-400 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters and Search -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<form method="get" action="<?= base_url('admin/news') ?>" class="flex flex-col gap-4 sm:flex-row sm:items-center">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
value="<?= esc($currentSearch ?? '') ?>"
|
||||
placeholder="Cari berita..."
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
name="status"
|
||||
class="h-11 rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
>
|
||||
<option value="">Semua Status</option>
|
||||
<option value="published" <?= ($currentStatus === 'published') ? 'selected' : '' ?>>Published</option>
|
||||
<option value="draft" <?= ($currentStatus === 'draft') ? 'selected' : '' ?>>Draft</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-search"></i>
|
||||
Cari
|
||||
</button>
|
||||
<?php if ($currentSearch || $currentStatus): ?>
|
||||
<a
|
||||
href="<?= base_url('admin/news') ?>"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
<i class="fe fe-x"></i>
|
||||
Reset
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- News Table -->
|
||||
<div class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="max-w-full overflow-x-auto">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-100 dark:border-gray-800">
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Judul
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Status
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Dibuat Oleh
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Tanggal Dibuat
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Aksi
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<?php if (empty($news)): ?>
|
||||
<tr>
|
||||
<td colspan="5" class="px-5 py-8 text-center sm:px-6">
|
||||
<p class="text-gray-500 dark:text-gray-400">Tidak ada berita ditemukan.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($news as $item): ?>
|
||||
<tr>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<p class="font-medium text-gray-800 text-sm dark:text-white/90">
|
||||
<?= esc($item['title']) ?>
|
||||
</p>
|
||||
<span class="text-gray-500 text-xs dark:text-gray-400">
|
||||
<?= esc($item['slug']) ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<?php if ($item['status'] === 'published'): ?>
|
||||
<p class="rounded-full bg-success-50 px-2 py-0.5 text-xs font-medium text-success-700 dark:bg-success-500/15 dark:text-success-500">
|
||||
Published
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<p class="rounded-full bg-warning-50 px-2 py-0.5 text-xs font-medium text-warning-700 dark:bg-warning-500/15 dark:text-warning-400">
|
||||
Draft
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="text-gray-500 text-sm dark:text-gray-400">
|
||||
<?= esc($item['creator_name'] ?? 'Unknown') ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="text-gray-500 text-sm dark:text-gray-400">
|
||||
<?= date('d M Y', strtotime($item['created_at'])) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
href="<?= base_url('admin/news/edit/' . $item['id']) ?>"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
title="Edit"
|
||||
>
|
||||
<i class="fe fe-edit text-sm"></i>
|
||||
<span class="hidden sm:inline">Edit</span>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
onclick="confirmDelete(<?= $item['id'] ?>)"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-error-300 bg-white px-3 py-1.5 text-sm font-medium text-error-700 shadow-theme-xs hover:bg-error-50 dark:border-error-700 dark:bg-gray-800 dark:text-error-400 dark:hover:bg-error-900/20"
|
||||
title="Hapus"
|
||||
>
|
||||
<i class="fe fe-trash-2 text-sm"></i>
|
||||
<span class="hidden sm:inline">Hapus</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<?php if ($pager->hasMore() || $pager->getCurrentPage() > 1): ?>
|
||||
<div class="flex items-center justify-between border-t border-gray-100 px-5 py-4 dark:border-gray-800 sm:px-6">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Menampilkan <?= count($news) ?> dari <?= $pager->getTotal() ?> berita
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<?= $pager->links() ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confirmation Modal -->
|
||||
<div id="confirmModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/50">
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-900 w-full max-w-md">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-2" id="confirmModalTitle">
|
||||
Hapus Berita
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4" id="confirmModalMessage">
|
||||
Apakah Anda yakin ingin menghapus berita ini? Tindakan ini tidak dapat dibatalkan.
|
||||
</p>
|
||||
<div class="flex items-center gap-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
id="confirmModalButton"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-error-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-error-600"
|
||||
>
|
||||
Ya, Hapus
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick="closeConfirmModal()"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
Batal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Form -->
|
||||
<form id="deleteForm" method="post" action="" style="display: none;">
|
||||
<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />
|
||||
</form>
|
||||
|
||||
<script>
|
||||
let confirmCallback = null;
|
||||
|
||||
function showConfirmModal(title, message, buttonText, buttonClass, callback) {
|
||||
document.getElementById('confirmModalTitle').textContent = title;
|
||||
document.getElementById('confirmModalMessage').textContent = message;
|
||||
const confirmBtn = document.getElementById('confirmModalButton');
|
||||
confirmBtn.textContent = buttonText;
|
||||
confirmBtn.className = `inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs ${buttonClass}`;
|
||||
confirmCallback = callback;
|
||||
document.getElementById('confirmModal').classList.remove('hidden');
|
||||
document.getElementById('confirmModal').classList.add('flex');
|
||||
}
|
||||
|
||||
function closeConfirmModal() {
|
||||
document.getElementById('confirmModal').classList.add('hidden');
|
||||
document.getElementById('confirmModal').classList.remove('flex');
|
||||
confirmCallback = null;
|
||||
}
|
||||
|
||||
function confirmDelete(id) {
|
||||
showConfirmModal(
|
||||
'Hapus Berita',
|
||||
'Apakah Anda yakin ingin menghapus berita ini? Tindakan ini tidak dapat dibatalkan.',
|
||||
'Ya, Hapus',
|
||||
'bg-error-500 hover:bg-error-600',
|
||||
function() {
|
||||
const form = document.getElementById('deleteForm');
|
||||
form.action = '<?= base_url('admin/news/delete/') ?>' + id;
|
||||
form.submit();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Handle confirm button click
|
||||
document.getElementById('confirmModalButton').addEventListener('click', function() {
|
||||
if (confirmCallback) {
|
||||
confirmCallback();
|
||||
closeConfirmModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal on outside click
|
||||
document.getElementById('confirmModal')?.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeConfirmModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
228
app/Views/admin/pages/form.php
Normal file
228
app/Views/admin/pages/form.php
Normal file
@@ -0,0 +1,228 @@
|
||||
<?= $this->extend('admin/layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-5 sm:space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white/90">
|
||||
<?= $page ? 'Edit Halaman' : 'Tambah Halaman' ?>
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<?= $page ? 'Ubah informasi halaman' : 'Tambahkan halaman baru' ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span id="autosave-indicator" class="hidden text-sm text-gray-500 dark:text-gray-400">Disimpan otomatis</span>
|
||||
<button
|
||||
type="button"
|
||||
id="preview-btn"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
<i class="fe fe-eye"></i>
|
||||
Preview
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<form
|
||||
action="<?= $page ? base_url('admin/pages/update/' . $page['id']) : base_url('admin/pages/store') ?>"
|
||||
method="post"
|
||||
class="space-y-6"
|
||||
id="page-form"
|
||||
>
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<!-- Main Content Area (2/3 width) -->
|
||||
<div class="lg:col-span-2 space-y-6">
|
||||
<!-- Title -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="p-5 sm:p-6">
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Judul <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="title"
|
||||
id="title"
|
||||
value="<?= old('title', $page['title'] ?? '') ?>"
|
||||
placeholder="Masukkan judul halaman"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
required
|
||||
>
|
||||
<?php if (isset($validation) && $validation->hasError('title')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('title')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Editor.js Container -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="p-5 sm:p-6">
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Konten <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<div id="editorjs" class="min-h-[500px] rounded-lg border border-gray-300 bg-white p-4 dark:border-gray-700 dark:bg-gray-900"></div>
|
||||
|
||||
<!-- Hidden inputs for Editor.js data -->
|
||||
<input type="hidden" name="content" id="content" value="<?= esc($page['content'] ?? '') ?>">
|
||||
<input type="hidden" name="content_json" id="content_json" value="<?= esc($page['content_json'] ?? '') ?>">
|
||||
<input type="hidden" name="content_html" id="content_html" value="<?= esc($page['content_html'] ?? '') ?>">
|
||||
<input type="hidden" name="excerpt" id="excerpt" value="<?= esc($page['excerpt'] ?? '') ?>">
|
||||
|
||||
<?php if (isset($validation) && $validation->hasError('content_json')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('content_json')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar (1/3 width) - Document Settings -->
|
||||
<div class="space-y-6">
|
||||
<!-- Status -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="p-5 sm:p-6">
|
||||
<h3 class="mb-4 text-sm font-semibold text-gray-700 dark:text-gray-300">Pengaturan Dokumen</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Status <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
name="status"
|
||||
id="status"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
required
|
||||
>
|
||||
<option value="draft" <?= old('status', $page['status'] ?? 'draft') === 'draft' ? 'selected' : '' ?>>Draft</option>
|
||||
<option value="published" <?= old('status', $page['status'] ?? '') === 'published' ? 'selected' : '' ?>>Published</option>
|
||||
</select>
|
||||
<?php if (isset($validation) && $validation->hasError('status')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('status')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Excerpt
|
||||
</label>
|
||||
<textarea
|
||||
name="excerpt"
|
||||
id="excerpt-textarea"
|
||||
rows="3"
|
||||
placeholder="Ringkasan halaman (otomatis dari konten pertama)"
|
||||
class="w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
><?= old('excerpt', $page['excerpt'] ?? '') ?></textarea>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">Akan diisi otomatis dari paragraf pertama</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Featured Image URL
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
name="featured_image"
|
||||
id="featured_image"
|
||||
value="<?= old('featured_image', $page['featured_image'] ?? '') ?>"
|
||||
placeholder="https://example.com/image.jpg"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
>
|
||||
<?php if (!empty($page['featured_image'] ?? '')): ?>
|
||||
<div class="mt-2">
|
||||
<img src="<?= esc($page['featured_image']) ?>" alt="Featured" class="h-24 w-full rounded-lg object-cover">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions - Match grid layout -->
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||
<div class="lg:col-span-2">
|
||||
<div class="flex items-center justify-end gap-3 rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<a
|
||||
href="<?= base_url('admin/pages') ?>"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
Batal
|
||||
</a>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-save"></i>
|
||||
<?= $page ? 'Simpan Perubahan' : 'Simpan Halaman' ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Editor.js Bundle (Built by Vite) -->
|
||||
<?php
|
||||
// Get manifest file to load hashed assets
|
||||
$manifestPath = FCPATH . 'assets/editor/.vite/manifest.json';
|
||||
$editorJsPath = base_url('assets/editor/editor.js'); // Fallback
|
||||
|
||||
if (file_exists($manifestPath)) {
|
||||
$manifest = json_decode(file_get_contents($manifestPath), true);
|
||||
if (isset($manifest['resources/js/editor/editor.js'])) {
|
||||
$editorJsPath = base_url('assets/editor/' . $manifest['resources/js/editor/editor.js']['file']);
|
||||
}
|
||||
}
|
||||
?>
|
||||
<script src="<?= $editorJsPath ?>"></script>
|
||||
<script>
|
||||
// CSRF & Endpoints for Editor.js
|
||||
window.csrfTokenName = '<?= csrf_token() ?>';
|
||||
window.csrfTokenValue = '<?= csrf_hash() ?>';
|
||||
window.csrfHeaderName = '<?= csrf_header() ?>';
|
||||
window.uploadEndpoint = '<?= base_url('admin/upload') ?>';
|
||||
window.linkPreviewEndpoint = '<?= base_url('admin/link-preview') ?>';
|
||||
window.pageId = <?= $page ? $page['id'] : 'null' ?>;
|
||||
|
||||
// Sync excerpt textarea with hidden input
|
||||
const excerptTextarea = document.getElementById('excerpt-textarea');
|
||||
const excerptInput = document.getElementById('excerpt');
|
||||
if (excerptTextarea && excerptInput) {
|
||||
excerptTextarea.addEventListener('input', function() {
|
||||
excerptInput.value = this.value;
|
||||
});
|
||||
}
|
||||
|
||||
// Fix Editor.js toolbar z-index to stay below header
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.ce-toolbar,
|
||||
.ce-inline-toolbar,
|
||||
.ce-popover,
|
||||
.ce-conversion-toolbar,
|
||||
.ce-settings,
|
||||
.ce-block-settings,
|
||||
.ce-toolbar__plus,
|
||||
.ce-toolbar__settings-btn,
|
||||
.ce-popover__item,
|
||||
.ce-popover__items,
|
||||
.ce-settings__button {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
header,
|
||||
header[class*="sticky"],
|
||||
header[class*="fixed"] {
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
</script>
|
||||
|
||||
<?= $this->endSection() ?>
|
||||
307
app/Views/admin/pages/index.php
Normal file
307
app/Views/admin/pages/index.php
Normal file
@@ -0,0 +1,307 @@
|
||||
<?= $this->extend('admin/layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-5 sm:space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white/90">
|
||||
Halaman
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Kelola halaman statis
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="<?= base_url('admin/pages/create') ?>"
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-plus"></i>
|
||||
Tambah Halaman
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Flash Messages sudah ditangani di layout.php -->
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Total Halaman</p>
|
||||
<p class="text-2xl font-bold text-gray-800 dark:text-white"><?= $stats['total'] ?></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-full bg-brand-100 dark:bg-brand-900/20 flex items-center justify-center">
|
||||
<i class="fe fe-file text-brand-600 dark:text-brand-400 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Published</p>
|
||||
<p class="text-2xl font-bold text-gray-800 dark:text-white"><?= $stats['published'] ?></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-full bg-success-100 dark:bg-success-900/20 flex items-center justify-center">
|
||||
<i class="fe fe-check-circle text-success-600 dark:text-success-400 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Draft</p>
|
||||
<p class="text-2xl font-bold text-gray-800 dark:text-white"><?= $stats['draft'] ?></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-full bg-warning-100 dark:bg-warning-900/20 flex items-center justify-center">
|
||||
<i class="fe fe-edit text-warning-600 dark:text-warning-400 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters and Search -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<form method="get" action="<?= base_url('admin/pages') ?>" class="flex flex-col gap-4 sm:flex-row sm:items-center">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
value="<?= esc($currentSearch ?? '') ?>"
|
||||
placeholder="Cari halaman..."
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
name="status"
|
||||
class="h-11 rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
>
|
||||
<option value="">Semua Status</option>
|
||||
<option value="published" <?= ($currentStatus === 'published') ? 'selected' : '' ?>>Published</option>
|
||||
<option value="draft" <?= ($currentStatus === 'draft') ? 'selected' : '' ?>>Draft</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-search"></i>
|
||||
Cari
|
||||
</button>
|
||||
<?php if ($currentSearch || $currentStatus): ?>
|
||||
<a
|
||||
href="<?= base_url('admin/pages') ?>"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
<i class="fe fe-x"></i>
|
||||
Reset
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Pages Table -->
|
||||
<div class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="max-w-full overflow-x-auto">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-100 dark:border-gray-800">
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Judul
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Status
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Tanggal Dibuat
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Aksi
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<?php if (empty($pages)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="px-5 py-8 text-center sm:px-6">
|
||||
<p class="text-gray-500 dark:text-gray-400">Tidak ada halaman ditemukan.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($pages as $item): ?>
|
||||
<tr>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<p class="font-medium text-gray-800 text-sm dark:text-white/90">
|
||||
<?= esc($item['title']) ?>
|
||||
</p>
|
||||
<span class="text-gray-500 text-xs dark:text-gray-400">
|
||||
<?= esc($item['slug']) ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<?php if ($item['status'] === 'published'): ?>
|
||||
<p class="rounded-full bg-success-50 px-2 py-0.5 text-xs font-medium text-success-700 dark:bg-success-500/15 dark:text-success-500">
|
||||
Published
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<p class="rounded-full bg-warning-50 px-2 py-0.5 text-xs font-medium text-warning-700 dark:bg-warning-500/15 dark:text-warning-400">
|
||||
Draft
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="text-gray-500 text-sm dark:text-gray-400">
|
||||
<?= date('d M Y', strtotime($item['created_at'])) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
href="<?= base_url('admin/pages/edit/' . $item['id']) ?>"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
title="Edit"
|
||||
>
|
||||
<i class="fe fe-edit text-sm"></i>
|
||||
<span class="hidden sm:inline">Edit</span>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
onclick="confirmDelete(<?= $item['id'] ?>)"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-error-300 bg-white px-3 py-1.5 text-sm font-medium text-error-700 shadow-theme-xs hover:bg-error-50 dark:border-error-700 dark:bg-gray-800 dark:text-error-400 dark:hover:bg-error-900/20"
|
||||
title="Hapus"
|
||||
>
|
||||
<i class="fe fe-trash-2 text-sm"></i>
|
||||
<span class="hidden sm:inline">Hapus</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<?php if ($pager->hasMore() || $pager->getCurrentPage() > 1): ?>
|
||||
<div class="flex items-center justify-between border-t border-gray-100 px-5 py-4 dark:border-gray-800 sm:px-6">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Menampilkan <?= count($pages) ?> dari <?= $pager->getTotal() ?> halaman
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<?= $pager->links() ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confirmation Modal -->
|
||||
<div id="confirmModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/50">
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-900 w-full max-w-md">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-2" id="confirmModalTitle">
|
||||
Hapus Halaman
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4" id="confirmModalMessage">
|
||||
Apakah Anda yakin ingin menghapus halaman ini? Tindakan ini tidak dapat dibatalkan.
|
||||
</p>
|
||||
<div class="flex items-center gap-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
id="confirmModalButton"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-error-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-error-600"
|
||||
>
|
||||
Ya, Hapus
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick="closeConfirmModal()"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
Batal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Form -->
|
||||
<form id="deleteForm" method="post" action="" style="display: none;">
|
||||
<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />
|
||||
</form>
|
||||
|
||||
<script>
|
||||
let confirmCallback = null;
|
||||
|
||||
function showConfirmModal(title, message, buttonText, buttonClass, callback) {
|
||||
document.getElementById('confirmModalTitle').textContent = title;
|
||||
document.getElementById('confirmModalMessage').textContent = message;
|
||||
const confirmBtn = document.getElementById('confirmModalButton');
|
||||
confirmBtn.textContent = buttonText;
|
||||
confirmBtn.className = `inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs ${buttonClass}`;
|
||||
confirmCallback = callback;
|
||||
document.getElementById('confirmModal').classList.remove('hidden');
|
||||
document.getElementById('confirmModal').classList.add('flex');
|
||||
}
|
||||
|
||||
function closeConfirmModal() {
|
||||
document.getElementById('confirmModal').classList.add('hidden');
|
||||
document.getElementById('confirmModal').classList.remove('flex');
|
||||
confirmCallback = null;
|
||||
}
|
||||
|
||||
function confirmDelete(id) {
|
||||
showConfirmModal(
|
||||
'Hapus Halaman',
|
||||
'Apakah Anda yakin ingin menghapus halaman ini? Tindakan ini tidak dapat dibatalkan.',
|
||||
'Ya, Hapus',
|
||||
'bg-error-500 hover:bg-error-600',
|
||||
function() {
|
||||
const form = document.getElementById('deleteForm');
|
||||
form.action = '<?= base_url('admin/pages/delete/') ?>' + id;
|
||||
form.submit();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Handle confirm button click
|
||||
document.getElementById('confirmModalButton').addEventListener('click', function() {
|
||||
if (confirmCallback) {
|
||||
confirmCallback();
|
||||
closeConfirmModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal on outside click
|
||||
document.getElementById('confirmModal')?.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeConfirmModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
111
app/Views/admin/partials/navbar.php
Normal file
111
app/Views/admin/partials/navbar.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<header
|
||||
x-data="{menuToggle: false}"
|
||||
class="sticky top-0 z-99999 flex w-full border-gray-200 bg-white lg:border-b dark:border-gray-800 dark:bg-gray-900"
|
||||
>
|
||||
<div class="flex grow flex-col items-center justify-between lg:flex-row lg:px-6">
|
||||
<div class="flex w-full items-center justify-between gap-2 border-b border-gray-200 px-3 py-3 sm:gap-4 lg:justify-normal lg:border-b-0 lg:px-0 lg:py-4 dark:border-gray-800">
|
||||
<!-- Hamburger Toggle BTN -->
|
||||
<button
|
||||
:class="sidebarToggle ? 'lg:bg-transparent dark:lg:bg-transparent bg-gray-100 dark:bg-gray-800' : ''"
|
||||
class="z-99999 flex h-10 w-10 items-center justify-center rounded-lg border-gray-200 text-gray-500 lg:h-11 lg:w-11 lg:border dark:border-gray-800 dark:text-gray-400"
|
||||
@click.stop="sidebarToggle = !sidebarToggle"
|
||||
>
|
||||
<i class="fe fe-menu hidden lg:block text-base"></i>
|
||||
<i :class="sidebarToggle ? 'hidden' : 'block lg:hidden'" class="fe fe-menu block lg:hidden text-lg"></i>
|
||||
<i :class="sidebarToggle ? 'block lg:hidden' : 'hidden'" class="fe fe-x block lg:hidden text-lg"></i>
|
||||
</button>
|
||||
<!-- Hamburger Toggle BTN -->
|
||||
|
||||
<!-- Page Title -->
|
||||
<div class="lg:hidden">
|
||||
<h2 class="text-lg font-semibold text-gray-800 dark:text-white"><?= esc($title ?? 'Dashboard') ?></h2>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="hidden lg:block">
|
||||
<form>
|
||||
<div class="relative">
|
||||
<span class="absolute top-1/2 left-4 -translate-y-1/2">
|
||||
<i class="fe fe-search text-gray-500 dark:text-gray-400 text-lg"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
class="h-11 w-full rounded-lg border border-gray-200 bg-transparent py-2.5 pr-4 pl-12 text-sm text-gray-800 placeholder:text-gray-400 focus:border-primary-300 focus:ring-3 focus:ring-primary-500/10 focus:outline-none xl:w-[430px] dark:border-gray-800 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="menuToggle ? 'flex' : 'hidden'" class="shadow-md w-full items-center justify-between gap-4 px-5 py-4 lg:flex lg:justify-end lg:px-0 lg:shadow-none">
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Dark Mode Toggler -->
|
||||
<button
|
||||
@click="$store.darkMode.toggle()"
|
||||
class="relative flex h-11 w-11 items-center justify-center rounded-full border border-gray-200 bg-white text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
>
|
||||
<i class="fe fe-sun hidden dark:block text-lg"></i>
|
||||
<i class="fe fe-moon dark:hidden text-lg"></i>
|
||||
</button>
|
||||
<!-- Dark Mode Toggler -->
|
||||
|
||||
<!-- Notification Menu Area -->
|
||||
<div class="relative" x-data="{ dropdownOpen: false }" @click.outside="dropdownOpen = false">
|
||||
<button
|
||||
class="relative flex h-11 w-11 items-center justify-center rounded-full border border-gray-200 bg-white text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
>
|
||||
<i class="fe fe-bell text-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Notification Menu Area -->
|
||||
</div>
|
||||
|
||||
<!-- User Area -->
|
||||
<div class="relative" x-data="{ dropdownOpen: false }" @click.outside="dropdownOpen = false">
|
||||
<a
|
||||
class="flex items-center text-gray-700 dark:text-gray-400"
|
||||
href="#"
|
||||
@click.prevent="dropdownOpen = !dropdownOpen"
|
||||
>
|
||||
<span class="mr-3 h-11 w-11 overflow-hidden rounded-full">
|
||||
<img src="<?= base_url('assets/images/user/owner.jpg') ?>" alt="User" class="h-full w-full object-cover" />
|
||||
</span>
|
||||
<span class="text-sm mr-1 block font-medium dark:text-white"><?= esc(session()->get('username') ?? 'User') ?></span>
|
||||
<i :class="dropdownOpen && 'rotate-180'" class="fe fe-chevron-down text-gray-500 dark:text-gray-400 text-sm transition-transform"></i>
|
||||
</a>
|
||||
|
||||
<!-- Dropdown Start -->
|
||||
<div
|
||||
x-show="dropdownOpen"
|
||||
class="shadow-lg absolute right-0 mt-[17px] flex w-[260px] flex-col rounded-2xl border border-gray-200 bg-white p-3 dark:border-gray-800 dark:bg-gray-900"
|
||||
>
|
||||
<div>
|
||||
<span class="text-sm block font-medium text-gray-700 dark:text-gray-400">
|
||||
<?= esc(session()->get('username') ?? 'User') ?>
|
||||
</span>
|
||||
<span class="text-xs mt-0.5 block text-gray-500 dark:text-gray-400">
|
||||
<?= esc(session()->get('email') ?? 'user@example.com') ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ul class="flex flex-col gap-1 border-b border-gray-200 pt-4 pb-3 dark:border-gray-800">
|
||||
<li>
|
||||
<a href="<?= base_url('admin/profile') ?>" class="group text-sm flex items-center gap-3 rounded-lg px-3 py-2 font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-white/5">
|
||||
<i class="fe fe-user text-gray-500 group-hover:text-gray-700 dark:text-gray-400"></i>
|
||||
Edit profile
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<a href="<?= base_url('auth/logout') ?>" class="group text-sm mt-3 flex items-center gap-3 rounded-lg px-3 py-2 font-medium text-red-600 hover:bg-gray-100 dark:text-red-400 dark:hover:bg-white/5">
|
||||
<i class="fe fe-log-out text-red-500 group-hover:text-red-700 dark:text-red-400"></i>
|
||||
Sign out
|
||||
</a>
|
||||
</div>
|
||||
<!-- Dropdown End -->
|
||||
</div>
|
||||
<!-- User Area -->
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
177
app/Views/admin/partials/sidebar.php
Normal file
177
app/Views/admin/partials/sidebar.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<aside
|
||||
:class="sidebarToggle ? 'translate-x-0 lg:w-[90px]' : '-translate-x-full'"
|
||||
class="sidebar fixed left-0 top-0 z-9999 flex h-screen w-[290px] flex-col overflow-y-hidden border-r border-gray-200 bg-white px-5 dark:border-gray-800 dark:bg-gray-900 lg:static lg:translate-x-0"
|
||||
>
|
||||
<!-- SIDEBAR HEADER -->
|
||||
<div
|
||||
:class="sidebarToggle ? 'justify-center' : 'justify-between'"
|
||||
class="flex items-center gap-2 pt-8 sidebar-header pb-7"
|
||||
>
|
||||
<a href="<?= base_url('admin/dashboard') ?>" class="flex items-center gap-3">
|
||||
<span class="logo" :class="sidebarToggle ? 'hidden' : ''">
|
||||
<img class="h-10 w-auto" src="<?= base_url('assets/images/logo/b_logo_1757803697487.png') ?>" alt="Logo Bapenda Garut" />
|
||||
</span>
|
||||
<img
|
||||
class="logo-icon h-10 w-10 object-contain"
|
||||
:class="sidebarToggle ? 'lg:block' : 'hidden'"
|
||||
src="<?= base_url('assets/images/logo/b_logo_1757803697487.png') ?>"
|
||||
alt="Logo"
|
||||
/>
|
||||
<?php
|
||||
// Get site name from settings
|
||||
$settingsModel = new \App\Models\SettingsModel();
|
||||
$siteName = $settingsModel->getSetting('site_name', 'Bapenda Garut');
|
||||
?>
|
||||
<span class="site-name text-2xl font-semibold text-gray-800 dark:text-white" :class="sidebarToggle ? 'lg:hidden' : ''">
|
||||
<?= esc($siteName) ?>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<!-- SIDEBAR HEADER -->
|
||||
|
||||
<div class="flex flex-col overflow-y-auto duration-300 ease-linear no-scrollbar">
|
||||
<!-- Sidebar Menu -->
|
||||
<nav>
|
||||
<?php
|
||||
// Get current URI segment
|
||||
$uri = service('uri');
|
||||
$segment1 = $uri->getSegment(1) ?? '';
|
||||
$segment2 = $uri->getSegment(2) ?? '';
|
||||
|
||||
// Determine active menu based on URI
|
||||
$activeMenu = '';
|
||||
if ($segment1 === 'admin') {
|
||||
if (empty($segment2) || $segment2 === 'dashboard') {
|
||||
$activeMenu = 'dashboard';
|
||||
} elseif ($segment2 === 'news') {
|
||||
$activeMenu = 'news';
|
||||
} elseif ($segment2 === 'pages') {
|
||||
$activeMenu = 'pages';
|
||||
} elseif ($segment2 === 'users') {
|
||||
$activeMenu = 'users';
|
||||
} elseif ($segment2 === 'audit-logs') {
|
||||
$activeMenu = 'audit-logs';
|
||||
} elseif ($segment2 === 'settings') {
|
||||
$activeMenu = 'settings';
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get active class
|
||||
$getActiveClass = function($menu) use ($activeMenu) {
|
||||
return $activeMenu === $menu
|
||||
? 'bg-primary-50 text-primary-600 dark:bg-white/5 dark:text-primary-400'
|
||||
: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-white/5';
|
||||
};
|
||||
?>
|
||||
<!-- Menu Group -->
|
||||
<div>
|
||||
<h3 class="mb-4 text-xs uppercase leading-[20px] text-gray-400">
|
||||
<span class="menu-group-title" :class="sidebarToggle ? 'lg:hidden' : ''">
|
||||
MENU
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<ul class="flex flex-col gap-0.5 mb-6">
|
||||
<!-- Menu Item Dashboard -->
|
||||
<li>
|
||||
<a
|
||||
href="<?= base_url('admin/dashboard') ?>"
|
||||
class="menu-item group flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium duration-300 <?= $getActiveClass('dashboard') ?>"
|
||||
>
|
||||
<span class="flex items-center justify-center w-6 h-6">
|
||||
<i class="fe fe-home text-xl"></i>
|
||||
</span>
|
||||
<span class="menu-item-text" :class="sidebarToggle ? 'lg:hidden' : ''">
|
||||
Dashboard
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Menu Item Dashboard -->
|
||||
|
||||
<!-- Menu Item News -->
|
||||
<li>
|
||||
<a
|
||||
href="<?= base_url('admin/news') ?>"
|
||||
class="menu-item group flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium duration-300 <?= $getActiveClass('news') ?>"
|
||||
>
|
||||
<span class="flex items-center justify-center w-6 h-6">
|
||||
<i class="fe fe-file-text text-xl"></i>
|
||||
</span>
|
||||
<span class="menu-item-text" :class="sidebarToggle ? 'lg:hidden' : ''">
|
||||
Berita
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Menu Item News -->
|
||||
|
||||
<!-- Menu Item Pages -->
|
||||
<li>
|
||||
<a
|
||||
href="<?= base_url('admin/pages') ?>"
|
||||
class="menu-item group flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium duration-300 <?= $getActiveClass('pages') ?>"
|
||||
>
|
||||
<span class="flex items-center justify-center w-6 h-6">
|
||||
<i class="fe fe-file text-xl"></i>
|
||||
</span>
|
||||
<span class="menu-item-text" :class="sidebarToggle ? 'lg:hidden' : ''">
|
||||
Halaman
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Menu Item Pages -->
|
||||
|
||||
<?php if (session()->get('role') === 'admin'): ?>
|
||||
<!-- Menu Item Users -->
|
||||
<li>
|
||||
<a
|
||||
href="<?= base_url('admin/users') ?>"
|
||||
class="menu-item group flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium duration-300 <?= $getActiveClass('users') ?>"
|
||||
>
|
||||
<span class="flex items-center justify-center w-6 h-6">
|
||||
<i class="fe fe-users text-xl"></i>
|
||||
</span>
|
||||
<span class="menu-item-text" :class="sidebarToggle ? 'lg:hidden' : ''">
|
||||
Pengguna
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Menu Item Users -->
|
||||
|
||||
<!-- Menu Item Audit Logs -->
|
||||
<li>
|
||||
<a
|
||||
href="<?= base_url('admin/audit-logs') ?>"
|
||||
class="menu-item group flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium duration-300 <?= $getActiveClass('audit-logs') ?>"
|
||||
>
|
||||
<span class="flex items-center justify-center w-6 h-6">
|
||||
<i class="fe fe-clipboard text-xl"></i>
|
||||
</span>
|
||||
<span class="menu-item-text" :class="sidebarToggle ? 'lg:hidden' : ''">
|
||||
Audit Log
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Menu Item Audit Logs -->
|
||||
|
||||
<!-- Menu Item Settings -->
|
||||
<li>
|
||||
<a
|
||||
href="<?= base_url('admin/settings') ?>"
|
||||
class="menu-item group flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium duration-300 <?= $getActiveClass('settings') ?>"
|
||||
>
|
||||
<span class="flex items-center justify-center w-6 h-6">
|
||||
<i class="fe fe-settings text-xl"></i>
|
||||
</span>
|
||||
<span class="menu-item-text" :class="sidebarToggle ? 'lg:hidden' : ''">
|
||||
Pengaturan
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<!-- Menu Item Settings -->
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Sidebar Menu -->
|
||||
</div>
|
||||
</aside>
|
||||
155
app/Views/admin/profile/index.php
Normal file
155
app/Views/admin/profile/index.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?= $this->extend('admin/layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-5 sm:space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white/90">
|
||||
Edit Profile
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Ubah informasi profile Anda
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="<?= base_url('admin/dashboard') ?>"
|
||||
class="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
<i class="fe fe-arrow-left"></i>
|
||||
Kembali
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Flash Messages -->
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="rounded-lg border border-error-200 bg-error-50 p-4 dark:border-error-800 dark:bg-error-900/20">
|
||||
<p class="text-sm text-error-800 dark:text-error-400">
|
||||
<?= esc(session()->getFlashdata('error')) ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="rounded-lg border border-success-200 bg-success-50 p-4 dark:border-success-800 dark:bg-success-900/20">
|
||||
<p class="text-sm text-success-800 dark:text-success-400">
|
||||
<?= esc(session()->getFlashdata('success')) ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Form -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="p-5 sm:p-6">
|
||||
<form
|
||||
action="<?= base_url('admin/profile/update') ?>"
|
||||
method="post"
|
||||
class="space-y-6"
|
||||
>
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<!-- Username -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Username <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
value="<?= old('username', $user['username'] ?? '') ?>"
|
||||
placeholder="Masukkan username"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
/>
|
||||
<?php if (session()->getFlashdata('errors') && isset(session()->getFlashdata('errors')['username'])): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc(session()->getFlashdata('errors')['username']) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Email <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value="<?= old('email', $user['email'] ?? '') ?>"
|
||||
placeholder="Masukkan email"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
/>
|
||||
<?php if (session()->getFlashdata('errors') && isset(session()->getFlashdata('errors')['email'])): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc(session()->getFlashdata('errors')['email']) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Password (optional) -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Password Baru
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Kosongkan jika tidak ingin mengubah password"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
minlength="6"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Minimal 6 karakter. Kosongkan jika tidak ingin mengubah password.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Phone Number -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Nomor Telepon
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="phone_number"
|
||||
value="<?= old('phone_number', $user['phone_number'] ?? '') ?>"
|
||||
placeholder="Masukkan nomor telepon"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Telegram ID -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Telegram ID
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="telegram_id"
|
||||
value="<?= old('telegram_id', $user['telegram_id'] ?? '') ?>"
|
||||
placeholder="Masukkan Telegram ID"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex items-center gap-3 border-t border-gray-100 pt-6 dark:border-gray-800">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-save"></i>
|
||||
Simpan Perubahan
|
||||
</button>
|
||||
<a
|
||||
href="<?= base_url('admin/dashboard') ?>"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
Batal
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
79
app/Views/admin/settings/index.php
Normal file
79
app/Views/admin/settings/index.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?= $this->extend('admin/layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-5 sm:space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white/90">
|
||||
Pengaturan
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Kelola pengaturan sistem
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="p-5 sm:p-6">
|
||||
<form
|
||||
action="<?= base_url('admin/settings/update') ?>"
|
||||
method="post"
|
||||
class="space-y-6"
|
||||
>
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<!-- Site Name -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Nama Situs <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="site_name"
|
||||
value="<?= esc(old('site_name', $settings['site_name']['value'] ?? 'Bapenda Garut')) ?>"
|
||||
placeholder="Masukkan nama situs"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
/>
|
||||
<?php if (session()->getFlashdata('errors') && isset(session()->getFlashdata('errors')['site_name'])): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc(session()->getFlashdata('errors')['site_name']) ?></p>
|
||||
<?php endif; ?>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Nama situs akan ditampilkan di sidebar dan judul halaman.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Site Description -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Deskripsi Situs
|
||||
</label>
|
||||
<textarea
|
||||
name="site_description"
|
||||
rows="3"
|
||||
placeholder="Masukkan deskripsi situs"
|
||||
class="w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
><?= esc(old('site_description', $settings['site_description']['value'] ?? '')) ?></textarea>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Deskripsi singkat tentang situs (opsional).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex items-center gap-3 border-t border-gray-100 pt-6 dark:border-gray-800">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-save"></i>
|
||||
Simpan Pengaturan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
224
app/Views/admin/users/form.php
Normal file
224
app/Views/admin/users/form.php
Normal file
@@ -0,0 +1,224 @@
|
||||
<?= $this->extend('admin/layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-5 sm:space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white/90">
|
||||
<?= $user ? 'Edit Pengguna' : 'Tambah Pengguna' ?>
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<?= $user ? 'Ubah informasi pengguna' : 'Tambahkan pengguna baru' ?>
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="<?= base_url('admin/users') ?>"
|
||||
class="inline-flex items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
<i class="fe fe-arrow-left"></i>
|
||||
Kembali
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Flash Messages -->
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="rounded-lg border border-error-200 bg-error-50 p-4 dark:border-error-800 dark:bg-error-900/20">
|
||||
<p class="text-sm text-error-800 dark:text-error-400">
|
||||
<?= esc(session()->getFlashdata('error')) ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Form -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="p-5 sm:p-6">
|
||||
<form
|
||||
action="<?= $user ? base_url('admin/users/update/' . $user['id']) : base_url('admin/users/store') ?>"
|
||||
method="post"
|
||||
class="space-y-6"
|
||||
>
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<!-- Username -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Username <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
value="<?= old('username', $user['username'] ?? '') ?>"
|
||||
placeholder="Masukkan username"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
/>
|
||||
<?php if (isset($validation) && $validation->hasError('username')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('username')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Email <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value="<?= old('email', $user['email'] ?? '') ?>"
|
||||
placeholder="Masukkan email"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
/>
|
||||
<?php if (isset($validation) && $validation->hasError('email')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('email')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Password (only for create) -->
|
||||
<?php if (!$user): ?>
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Password <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Masukkan password (min 6 karakter)"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
minlength="6"
|
||||
/>
|
||||
<?php if (isset($validation) && $validation->hasError('password')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('password')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Role -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Role <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
name="role_id"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
required
|
||||
>
|
||||
<option value="">Pilih Role</option>
|
||||
<?php foreach ($roles as $role): ?>
|
||||
<option value="<?= $role['id'] ?>" <?= old('role_id', $user['role_id'] ?? '') == $role['id'] ? 'selected' : '' ?>>
|
||||
<?= esc(ucfirst($role['name'])) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if (isset($validation) && $validation->hasError('role_id')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('role_id')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Phone Number -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Nomor Telepon
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="phone_number"
|
||||
value="<?= old('phone_number', $user['phone_number'] ?? '') ?>"
|
||||
placeholder="Masukkan nomor telepon"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
<?php if (isset($validation) && $validation->hasError('phone_number')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('phone_number')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Telegram ID -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Telegram ID
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="telegram_id"
|
||||
value="<?= old('telegram_id', $user['telegram_id'] ?? '') ?>"
|
||||
placeholder="Masukkan Telegram ID"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
<?php if (isset($validation) && $validation->hasError('telegram_id')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('telegram_id')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Active Status -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Status
|
||||
</label>
|
||||
<div class="flex items-center gap-4">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="is_active"
|
||||
value="1"
|
||||
<?= old('is_active', $user['is_active'] ?? 1) ? 'checked' : '' ?>
|
||||
class="sr-only"
|
||||
/>
|
||||
<div class="relative">
|
||||
<div class="block h-8 w-14 rounded-full <?= old('is_active', $user['is_active'] ?? 1) ? 'bg-brand-500' : 'bg-gray-300 dark:bg-gray-700' ?> transition-colors"></div>
|
||||
<div class="absolute left-1 top-1 h-6 w-6 rounded-full bg-white transition-transform <?= old('is_active', $user['is_active'] ?? 1) ? 'translate-x-6' : '' ?>"></div>
|
||||
</div>
|
||||
<span class="ml-3 text-sm text-gray-700 dark:text-gray-400">
|
||||
<?= old('is_active', $user['is_active'] ?? 1) ? 'Aktif' : 'Tidak Aktif' ?>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="flex items-center gap-3 border-t border-gray-100 pt-6 dark:border-gray-800">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-save"></i>
|
||||
<?= $user ? 'Simpan Perubahan' : 'Simpan Pengguna' ?>
|
||||
</button>
|
||||
<a
|
||||
href="<?= base_url('admin/users') ?>"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
Batal
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Toggle switch functionality
|
||||
document.querySelector('input[name="is_active"]')?.addEventListener('change', function() {
|
||||
const toggle = this.closest('label').querySelector('.block');
|
||||
const circle = this.closest('label').querySelector('.absolute');
|
||||
const text = this.closest('label').querySelector('span');
|
||||
|
||||
if (this.checked) {
|
||||
toggle.classList.add('bg-brand-500');
|
||||
toggle.classList.remove('bg-gray-300', 'dark:bg-gray-700');
|
||||
circle.classList.add('translate-x-6');
|
||||
text.textContent = 'Aktif';
|
||||
} else {
|
||||
toggle.classList.remove('bg-brand-500');
|
||||
toggle.classList.add('bg-gray-300', 'dark:bg-gray-700');
|
||||
circle.classList.remove('translate-x-6');
|
||||
text.textContent = 'Tidak Aktif';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
460
app/Views/admin/users/index.php
Normal file
460
app/Views/admin/users/index.php
Normal file
@@ -0,0 +1,460 @@
|
||||
<?= $this->extend('admin/layout') ?>
|
||||
|
||||
<?= $this->section('content') ?>
|
||||
<div class="space-y-5 sm:space-y-6">
|
||||
<!-- Page Header -->
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 dark:text-white/90">
|
||||
Pengguna
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Kelola pengguna sistem
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="<?= base_url('admin/users/create') ?>"
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-plus"></i>
|
||||
Tambah Pengguna
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Total Pengguna</p>
|
||||
<p class="text-2xl font-bold text-gray-800 dark:text-white"><?= $stats['total'] ?></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-full bg-brand-100 dark:bg-brand-900/20 flex items-center justify-center">
|
||||
<i class="fe fe-users text-brand-600 dark:text-brand-400 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Aktif</p>
|
||||
<p class="text-2xl font-bold text-gray-800 dark:text-white"><?= $stats['active'] ?></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-full bg-success-100 dark:bg-success-900/20 flex items-center justify-center">
|
||||
<i class="fe fe-check-circle text-success-600 dark:text-success-400 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Tidak Aktif</p>
|
||||
<p class="text-2xl font-bold text-gray-800 dark:text-white"><?= $stats['inactive'] ?></p>
|
||||
</div>
|
||||
<div class="w-12 h-12 rounded-full bg-error-100 dark:bg-error-900/20 flex items-center justify-center">
|
||||
<i class="fe fe-x-circle text-error-600 dark:text-error-400 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters and Search -->
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<form method="get" action="<?= base_url('admin/users') ?>" class="flex flex-col gap-4 sm:flex-row sm:items-center">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
name="search"
|
||||
value="<?= esc($currentSearch ?? '') ?>"
|
||||
placeholder="Cari pengguna..."
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
name="role"
|
||||
class="h-11 rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
>
|
||||
<option value="">Semua Role</option>
|
||||
<?php foreach ($roles as $role): ?>
|
||||
<option value="<?= esc($role['name']) ?>" <?= ($currentRole === $role['name']) ? 'selected' : '' ?>>
|
||||
<?= esc(ucfirst($role['name'])) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
name="status"
|
||||
class="h-11 rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:focus:border-brand-800"
|
||||
>
|
||||
<option value="">Semua Status</option>
|
||||
<option value="1" <?= ($currentStatus === '1') ? 'selected' : '' ?>>Aktif</option>
|
||||
<option value="0" <?= ($currentStatus === '0') ? 'selected' : '' ?>>Tidak Aktif</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
<i class="fe fe-search"></i>
|
||||
Cari
|
||||
</button>
|
||||
<?php if ($currentSearch || $currentRole || $currentStatus !== null): ?>
|
||||
<a
|
||||
href="<?= base_url('admin/users') ?>"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
<i class="fe fe-x"></i>
|
||||
Reset
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Users Table -->
|
||||
<div class="overflow-hidden rounded-xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03]">
|
||||
<div class="max-w-full overflow-x-auto">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-100 dark:border-gray-800">
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Username
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Email
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Role
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Status
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
<th class="px-5 py-3 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="font-medium text-gray-500 text-xs dark:text-gray-400">
|
||||
Aksi
|
||||
</p>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<?php if (empty($users)): ?>
|
||||
<tr>
|
||||
<td colspan="5" class="px-5 py-8 text-center sm:px-6">
|
||||
<p class="text-gray-500 dark:text-gray-400">Tidak ada pengguna ditemukan.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else: ?>
|
||||
<?php foreach ($users as $item): ?>
|
||||
<tr>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<p class="font-medium text-gray-800 text-sm dark:text-white/90">
|
||||
<?= esc($item['username']) ?>
|
||||
</p>
|
||||
<?php if (!empty($item['phone_number'])): ?>
|
||||
<span class="text-gray-500 text-xs dark:text-gray-400">
|
||||
<?= esc($item['phone_number']) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="text-gray-500 text-sm dark:text-gray-400">
|
||||
<?= esc($item['email']) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<p class="rounded-full bg-brand-50 px-2 py-0.5 text-xs font-medium text-brand-700 dark:bg-brand-500/15 dark:text-brand-500">
|
||||
<?= esc(ucfirst($item['role_name'] ?? 'Unknown')) ?>
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center">
|
||||
<?php if ($item['is_active']): ?>
|
||||
<p class="rounded-full bg-success-50 px-2 py-0.5 text-xs font-medium text-success-700 dark:bg-success-500/15 dark:text-success-500">
|
||||
Aktif
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<p class="rounded-full bg-error-50 px-2 py-0.5 text-xs font-medium text-error-700 dark:bg-error-500/15 dark:text-error-500">
|
||||
Tidak Aktif
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 sm:px-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
href="<?= base_url('admin/users/edit/' . $item['id']) ?>"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
title="Edit"
|
||||
>
|
||||
<i class="fe fe-edit text-sm"></i>
|
||||
<span class="hidden sm:inline">Edit</span>
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
onclick="showResetPasswordModal(<?= $item['id'] ?>, '<?= esc($item['username']) ?>')"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-warning-300 bg-white px-3 py-1.5 text-sm font-medium text-warning-700 shadow-theme-xs hover:bg-warning-50 dark:border-warning-700 dark:bg-gray-800 dark:text-warning-400 dark:hover:bg-warning-900/20"
|
||||
title="Reset Password"
|
||||
>
|
||||
<i class="fe fe-lock text-sm"></i>
|
||||
<span class="hidden sm:inline">Reset</span>
|
||||
</button>
|
||||
<?php if ($item['id'] != session()->get('user_id')): ?>
|
||||
<button
|
||||
type="button"
|
||||
onclick="toggleActive(<?= $item['id'] ?>, <?= $item['is_active'] ? 0 : 1 ?>)"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border <?= $item['is_active'] ? 'border-error-300 text-error-700 hover:bg-error-50' : 'border-success-300 text-success-700 hover:bg-success-50' ?> bg-white px-3 py-1.5 text-sm font-medium shadow-theme-xs dark:bg-gray-800 dark:hover:bg-white/[0.03]"
|
||||
title="<?= $item['is_active'] ? 'Nonaktifkan' : 'Aktifkan' ?>"
|
||||
>
|
||||
<i class="fe <?= $item['is_active'] ? 'fe-x-circle' : 'fe-check-circle' ?> text-sm"></i>
|
||||
<span class="hidden sm:inline"><?= $item['is_active'] ? 'Nonaktif' : 'Aktif' ?></span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick="deleteUser(<?= $item['id'] ?>, '<?= esc($item['username']) ?>')"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-error-300 bg-white px-3 py-1.5 text-sm font-medium text-error-700 shadow-theme-xs hover:bg-error-50 dark:border-error-700 dark:bg-gray-800 dark:text-error-400 dark:hover:bg-error-900/20"
|
||||
title="Hapus"
|
||||
>
|
||||
<i class="fe fe-trash-2 text-sm"></i>
|
||||
<span class="hidden sm:inline">Hapus</span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<?php if ($pager->hasMore() || $pager->getCurrentPage() > 1): ?>
|
||||
<div class="flex items-center justify-between border-t border-gray-100 px-5 py-4 dark:border-gray-800 sm:px-6">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Menampilkan <?= count($users) ?> dari <?= $pager->getTotal() ?> pengguna
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<?= $pager->links() ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confirmation Modal -->
|
||||
<div id="confirmModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/50">
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-900 w-full max-w-md">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-2" id="confirmModalTitle">
|
||||
Konfirmasi
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4" id="confirmModalMessage">
|
||||
Apakah Anda yakin?
|
||||
</p>
|
||||
<div class="flex items-center gap-3 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
id="confirmModalButton"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
Ya, Lanjutkan
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick="closeConfirmModal()"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
Batal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reset Password Modal -->
|
||||
<div id="resetPasswordModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/50">
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-800 dark:bg-gray-900 w-full max-w-md">
|
||||
<h3 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">
|
||||
Reset Password
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
||||
Reset password untuk: <span id="resetUsername" class="font-medium"></span>
|
||||
</p>
|
||||
<form id="resetPasswordForm" method="post" action="" class="space-y-4">
|
||||
<?= csrf_field() ?>
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Password Baru <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="new_password"
|
||||
placeholder="Masukkan password baru"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
minlength="6"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400">
|
||||
Konfirmasi Password <span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="confirm_password"
|
||||
placeholder="Konfirmasi password baru"
|
||||
class="h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:ring-3 focus:ring-brand-500/10 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
minlength="6"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 pt-4">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
Reset Password
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick="closeResetPasswordModal()"
|
||||
class="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-300 bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-theme-xs hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-white/[0.03]"
|
||||
>
|
||||
Batal
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showResetPasswordModal(userId, username) {
|
||||
document.getElementById('resetUsername').textContent = username;
|
||||
document.getElementById('resetPasswordForm').action = '<?= base_url('admin/users/reset-password/') ?>' + userId;
|
||||
document.getElementById('resetPasswordModal').classList.remove('hidden');
|
||||
document.getElementById('resetPasswordModal').classList.add('flex');
|
||||
}
|
||||
|
||||
function closeResetPasswordModal() {
|
||||
document.getElementById('resetPasswordModal').classList.add('hidden');
|
||||
document.getElementById('resetPasswordModal').classList.remove('flex');
|
||||
document.getElementById('resetPasswordForm').reset();
|
||||
}
|
||||
|
||||
let confirmCallback = null;
|
||||
|
||||
function showConfirmModal(title, message, buttonText, buttonClass, callback) {
|
||||
document.getElementById('confirmModalTitle').textContent = title;
|
||||
document.getElementById('confirmModalMessage').textContent = message;
|
||||
const confirmBtn = document.getElementById('confirmModalButton');
|
||||
confirmBtn.textContent = buttonText;
|
||||
confirmBtn.className = `inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-medium text-white shadow-theme-xs hover:opacity-90 ${buttonClass}`;
|
||||
confirmCallback = callback;
|
||||
document.getElementById('confirmModal').classList.remove('hidden');
|
||||
document.getElementById('confirmModal').classList.add('flex');
|
||||
}
|
||||
|
||||
function closeConfirmModal() {
|
||||
document.getElementById('confirmModal').classList.add('hidden');
|
||||
document.getElementById('confirmModal').classList.remove('flex');
|
||||
confirmCallback = null;
|
||||
}
|
||||
|
||||
function toggleActive(userId, newStatus) {
|
||||
const action = newStatus ? 'mengaktifkan' : 'menonaktifkan';
|
||||
const actionText = newStatus ? 'mengaktifkan' : 'menonaktifkan';
|
||||
const buttonClass = newStatus ? 'bg-success-500 hover:bg-success-600' : 'bg-warning-500 hover:bg-warning-600';
|
||||
|
||||
showConfirmModal(
|
||||
'Konfirmasi',
|
||||
`Apakah Anda yakin ingin ${actionText} pengguna ini?`,
|
||||
'Ya, Lanjutkan',
|
||||
buttonClass,
|
||||
function() {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '<?= base_url('admin/users/toggle-active/') ?>' + userId;
|
||||
|
||||
const csrf = document.createElement('input');
|
||||
csrf.type = 'hidden';
|
||||
csrf.name = '<?= csrf_token() ?>';
|
||||
csrf.value = '<?= csrf_hash() ?>';
|
||||
form.appendChild(csrf);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function deleteUser(userId, username) {
|
||||
showConfirmModal(
|
||||
'Hapus Pengguna',
|
||||
`Apakah Anda yakin ingin menghapus pengguna "${username}"? Tindakan ini tidak dapat dibatalkan.`,
|
||||
'Ya, Hapus',
|
||||
'bg-error-500 hover:bg-error-600',
|
||||
function() {
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '<?= base_url('admin/users/delete/') ?>' + userId;
|
||||
|
||||
const csrf = document.createElement('input');
|
||||
csrf.type = 'hidden';
|
||||
csrf.name = '<?= csrf_token() ?>';
|
||||
csrf.value = '<?= csrf_hash() ?>';
|
||||
form.appendChild(csrf);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Handle confirm button click
|
||||
document.getElementById('confirmModalButton').addEventListener('click', function() {
|
||||
if (confirmCallback) {
|
||||
confirmCallback();
|
||||
closeConfirmModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Close modals on outside click
|
||||
document.getElementById('resetPasswordModal')?.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeResetPasswordModal();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('confirmModal')?.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeConfirmModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
331
app/Views/auth/login.php
Normal file
331
app/Views/auth/login.php
Normal file
@@ -0,0 +1,331 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
|
||||
/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<meta name="csrf-token" content="<?= csrf_hash() ?>" />
|
||||
<meta name="csrf-header" content="<?= csrf_header() ?>" />
|
||||
<title>Sign In - Bapenda Garut</title>
|
||||
<link rel="icon" type="image/png" href="<?= base_url('assets/images/favicon_1762970389090.png') ?>" />
|
||||
<link rel="shortcut icon" type="image/png" href="<?= base_url('assets/images/favicon_1762970389090.png') ?>" />
|
||||
<link rel="stylesheet" href="<?= base_url('assets/css/app.css') ?>">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
</head>
|
||||
<body
|
||||
x-data="{ page: 'comingSoon', 'loaded': true, 'darkMode': false, 'stickyMenu': false, 'sidebarToggle': false, 'scrollTop': false, 'showPassword': false }"
|
||||
x-init="
|
||||
darkMode = JSON.parse(localStorage.getItem('darkMode') || 'false');
|
||||
$watch('darkMode', value => localStorage.setItem('darkMode', JSON.stringify(value)))"
|
||||
:class="{'dark bg-gray-900': darkMode === true}"
|
||||
>
|
||||
<!-- ===== Page Wrapper Start ===== -->
|
||||
<div class="relative p-6 bg-white z-1 dark:bg-gray-900 sm:p-0">
|
||||
<div
|
||||
class="relative flex flex-col justify-center w-full h-screen dark:bg-gray-900 sm:p-0 lg:flex-row"
|
||||
>
|
||||
<!-- Form -->
|
||||
<div class="flex flex-col flex-1 w-full lg:w-1/2">
|
||||
<div class="w-full max-w-md pt-10 mx-auto">
|
||||
<a
|
||||
href="<?= base_url('/') ?>"
|
||||
class="inline-flex items-center text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
|
||||
>
|
||||
<i class="fe fe-arrow-left"></i>
|
||||
<span class="ml-2">Kembali ke Beranda</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col justify-center flex-1 w-full max-w-md mx-auto"
|
||||
>
|
||||
<div>
|
||||
<div class="mb-5 sm:mb-8">
|
||||
<h1
|
||||
class="mb-2 font-semibold text-gray-800 text-title-sm dark:text-white/90 sm:text-title-md"
|
||||
>
|
||||
Sign In
|
||||
</h1>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Masukkan username dan password anda!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Flash Messages -->
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="mb-4 p-4 rounded-lg border border-error-200 bg-error-50 dark:border-error-800 dark:bg-error-900/20 shadow-theme-sm">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="flex items-center justify-center w-5 h-5 rounded-full bg-error-100 dark:bg-error-900/40">
|
||||
<i class="fe fe-alert-circle text-error-600 dark:text-error-400 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-semibold text-error-800 dark:text-error-200">
|
||||
<?= esc(session()->getFlashdata('error')) ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="mb-4 p-4 rounded-lg border border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-900/20 shadow-theme-sm">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="flex items-center justify-center w-5 h-5 rounded-full bg-green-100 dark:bg-green-900/40">
|
||||
<i class="fe fe-check-circle text-green-600 dark:text-green-400 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-semibold text-green-800 dark:text-green-200">
|
||||
<?= esc(session()->getFlashdata('success')) ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Validation Errors -->
|
||||
<?php if (isset($validation) && !empty($validation->getErrors())): ?>
|
||||
<?php foreach ($validation->getErrors() as $field => $error): ?>
|
||||
<div class="mb-4 p-4 rounded-lg border border-error-200 bg-error-50 dark:border-error-800 dark:bg-error-900/20 shadow-theme-sm">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="flex items-center justify-center w-5 h-5 rounded-full bg-error-100 dark:bg-error-900/40">
|
||||
<i class="fe fe-alert-circle text-error-600 dark:text-error-400 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-semibold text-error-800 dark:text-error-200">
|
||||
<?= esc($error) ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$hasValidationErrors = isset($validation) && !empty($validation->getErrors());
|
||||
if (isset($error) && !empty($error) && !$hasValidationErrors):
|
||||
?>
|
||||
<div class="mb-4 p-4 rounded-lg border border-error-200 bg-error-50 dark:border-error-800 dark:bg-error-900/20 shadow-theme-sm">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="flex items-center justify-center w-5 h-5 rounded-full bg-error-100 dark:bg-error-900/40">
|
||||
<i class="fe fe-alert-circle text-error-600 dark:text-error-400 text-sm"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-semibold text-error-800 dark:text-error-200">
|
||||
<?= esc($error) ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Debug: Always show this to test styling -->
|
||||
<?php if (isset($_GET['debug'])): ?>
|
||||
<div class="mb-4 p-4 rounded-lg border-2 border-error-500 bg-error-50 dark:border-error-500 dark:bg-error-900/30">
|
||||
<p class="text-sm font-medium text-error-800 dark:text-error-300">
|
||||
<i class="fe fe-alert-circle mr-2"></i>
|
||||
DEBUG: Error message styling test - If you see this, styling works!
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Test: Always show error message for debugging (REMOVE AFTER TESTING) -->
|
||||
<!--
|
||||
<div class="mb-4 p-4 rounded-lg border-2 border-error-500 bg-error-50 dark:border-error-500 dark:bg-error-900/30" style="display: block !important; background: #fee2e2 !important; border-color: #ef4444 !important;">
|
||||
<p class="text-sm font-medium text-error-800 dark:text-error-300" style="color: #991b1b !important;">
|
||||
<i class="fe fe-alert-circle mr-2"></i>
|
||||
TEST: Error message test - Jika ini muncul, styling bekerja!
|
||||
</p>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<form action="<?= base_url('auth/login') ?>" method="POST" id="loginForm">
|
||||
<?= csrf_field() ?>
|
||||
<div class="space-y-5">
|
||||
<!-- Username -->
|
||||
<div>
|
||||
<label
|
||||
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
|
||||
>
|
||||
Username<span class="text-error-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
value="<?= old('username') ?>"
|
||||
placeholder="Masukkan username"
|
||||
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent px-4 py-2.5 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
autofocus
|
||||
/>
|
||||
<?php if (isset($validation) && $validation->hasError('username')): ?>
|
||||
<p class="mt-1 text-sm text-error-600"><?= esc($validation->getError('username')) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<!-- Password -->
|
||||
<div>
|
||||
<label
|
||||
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-400"
|
||||
>
|
||||
Password<span class="text-error-500">*</span>
|
||||
</label>
|
||||
<div x-data="{ showPassword: false }" class="relative">
|
||||
<input
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Masukkan password"
|
||||
class="dark:bg-dark-900 h-11 w-full rounded-lg border border-gray-300 bg-transparent py-2.5 pl-4 pr-11 text-sm text-gray-800 shadow-theme-xs placeholder:text-gray-400 focus:border-brand-300 focus:outline-hidden focus:ring-3 focus:ring-brand-500/10 dark:border-gray-700 dark:bg-gray-900 dark:text-white/90 dark:placeholder:text-white/30 dark:focus:border-brand-800"
|
||||
required
|
||||
/>
|
||||
<span
|
||||
@click="showPassword = !showPassword"
|
||||
class="absolute z-30 text-gray-500 -translate-y-1/2 cursor-pointer right-4 top-1/2 dark:text-gray-400"
|
||||
>
|
||||
<i x-show="!showPassword" class="fe fe-eye text-lg"></i>
|
||||
<i x-show="showPassword" class="fe fe-eye-off text-lg"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Checkbox -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div x-data="{ checkboxToggle: false }">
|
||||
<label
|
||||
for="checkboxLabelOne"
|
||||
class="flex items-center text-sm font-normal text-gray-700 cursor-pointer select-none dark:text-gray-400"
|
||||
>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checkboxLabelOne"
|
||||
name="remember"
|
||||
class="sr-only"
|
||||
@change="checkboxToggle = !checkboxToggle"
|
||||
/>
|
||||
<div
|
||||
:class="checkboxToggle ? 'border-brand-500 bg-brand-500' : 'bg-transparent border-gray-300 dark:border-gray-700'"
|
||||
class="mr-3 flex h-5 w-5 items-center justify-center rounded-md border-[1.25px]"
|
||||
>
|
||||
<span :class="checkboxToggle ? '' : 'opacity-0'">
|
||||
<i class="fe fe-check text-white text-xs"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
biarkan tetap masuk
|
||||
</label>
|
||||
</div>
|
||||
<a
|
||||
href="#"
|
||||
class="text-sm text-brand-500 hover:text-brand-600 dark:text-brand-400"
|
||||
>lupa password?</a
|
||||
>
|
||||
</div>
|
||||
<!-- Button -->
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
class="flex items-center justify-center w-full px-4 py-3 text-sm font-medium text-white transition rounded-lg bg-brand-500 shadow-theme-xs hover:bg-brand-600"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative items-center hidden w-full h-full bg-brand-950 dark:bg-white/5 lg:grid lg:w-1/2"
|
||||
>
|
||||
<div class="flex items-center justify-center z-1">
|
||||
<!-- ===== Common Grid Shape Start ===== -->
|
||||
<div class="absolute right-0 top-0 -z-1 w-full max-w-[250px] xl:max-w-[450px]">
|
||||
<img src="<?= base_url('assets/images/shape/grid-01.svg') ?>" alt="grid" />
|
||||
</div>
|
||||
<div
|
||||
class="absolute bottom-0 left-0 -z-1 w-full max-w-[250px] rotate-180 xl:max-w-[450px]"
|
||||
>
|
||||
<img src="<?= base_url('assets/images/shape/grid-01.svg') ?>" alt="grid" />
|
||||
</div>
|
||||
<!-- ===== Common Grid Shape End ===== -->
|
||||
<div class="flex flex-col items-center max-w-xs relative z-10">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<a href="<?= base_url('/') ?>" class="block">
|
||||
<img src="<?= base_url('assets/images/logo/b_logo_1757803697487.png') ?>" alt="Logo Bapenda Garut" class="h-12 w-auto" />
|
||||
</a>
|
||||
<h2 class="text-2xl font-bold text-gray-400 dark:text-white">Bapenda Garut</h2>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Toggler -->
|
||||
<div class="fixed z-50 hidden bottom-6 right-6 sm:block">
|
||||
<button
|
||||
class="inline-flex items-center justify-center text-white transition-colors rounded-full w-14 h-14 bg-brand-500 hover:bg-brand-600"
|
||||
@click.prevent="darkMode = !darkMode"
|
||||
>
|
||||
<i class="fe fe-sun text-lg hidden dark:block"></i>
|
||||
<i class="fe fe-moon text-lg dark:hidden"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ===== Page Wrapper End ===== -->
|
||||
|
||||
<script>
|
||||
// Debug form submission
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('loginForm');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
console.log('=== FORM SUBMISSION DEBUG ===');
|
||||
console.log('Form action:', this.action);
|
||||
console.log('Form method:', this.method);
|
||||
const formData = new FormData(this);
|
||||
const username = formData.get('username');
|
||||
const password = formData.get('password');
|
||||
console.log('Username:', username || 'EMPTY!');
|
||||
console.log('Password:', password ? '***filled***' : 'EMPTY!');
|
||||
|
||||
// Check CSRF token
|
||||
const csrfToken = formData.get('<?= csrf_token() ?>');
|
||||
console.log('CSRF token:', csrfToken ? 'exists' : 'MISSING!');
|
||||
|
||||
// Prevent submission if empty
|
||||
if (!username || !password) {
|
||||
console.error('ERROR: Username or password is empty!');
|
||||
alert('Username dan password harus diisi!');
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Form will be submitted...');
|
||||
console.log('===========================');
|
||||
});
|
||||
} else {
|
||||
console.error('ERROR: Login form not found!');
|
||||
}
|
||||
|
||||
// Check if error message exists
|
||||
const errorDiv = document.querySelector('[class*="error-"]');
|
||||
if (errorDiv) {
|
||||
console.log('Error message div found:', errorDiv.textContent);
|
||||
} else {
|
||||
console.log('No error message div found');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
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>
|
||||
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