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() ?>
|
||||
|
||||
Reference in New Issue
Block a user