Files
webgis_geospesial/dashboard.php
T

631 lines
44 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
$role = $_SESSION['role'] ?? 'guest';
$username = $_SESSION['username'] ?? 'User';
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard WebGIS Pengentasan Kemiskinan</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['"Plus Jakarta Sans"', 'sans-serif'],
heading: ['"Plus Jakarta Sans"', 'sans-serif'],
},
colors: {
background: '#f8fafc',
foreground: '#0f172a',
primary: { DEFAULT: '#e11d48', foreground: '#ffffff' },
muted: { DEFAULT: '#f1f5f9', foreground: '#64748b' },
border: '#e2e8f0',
}
}
}
}
</script>
<style>
/* Custom Pink Scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #e11d48; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #be123c; }
</style>
</head>
<body class="bg-background font-sans text-foreground h-screen flex flex-col relative overflow-hidden selection:bg-primary/20 selection:text-primary">
<!-- Ambient Background Effects -->
<div aria-hidden="true" class="pointer-events-none fixed inset-0 z-0">
<div class="absolute -top-40 -right-40 h-96 w-96 rounded-full bg-primary/10 blur-[100px]"></div>
<div class="absolute top-1/3 -left-40 h-96 w-96 rounded-full bg-rose-500/10 blur-[100px]"></div>
<div class="absolute -bottom-40 left-1/2 h-96 w-96 -translate-x-1/2 rounded-full bg-primary/5 blur-[100px]"></div>
<!-- Grid pattern -->
<div class="absolute inset-0 opacity-[0.03]" style="background-image: linear-gradient(to right, #0f172a 1px, transparent 1px), linear-gradient(to bottom, #0f172a 1px, transparent 1px); background-size: 40px 40px;"></div>
</div>
<!-- Header Navigation -->
<header class="relative z-10 w-full border-b border-border/60 bg-white/60 backdrop-blur-xl">
<div class="mx-auto flex h-16 max-w-6xl items-center justify-between px-6 lg:px-8">
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-primary to-rose-700 text-white shadow-md shadow-primary/20">
<svg viewBox="0 0 24 24" fill="none" class="h-5 w-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M12 21s-7-5.6-7-11a7 7 0 1 1 14 0c0 5.4-7 11-7 11Z" />
<circle cx="12" cy="10" r="2.5" />
</svg>
</div>
<div>
<h1 class="font-heading text-sm font-bold tracking-wide text-foreground">WebGIS Geospasial</h1>
<p class="text-[11px] font-medium text-muted-foreground uppercase tracking-wider">Kementerian Analisis Spasial</p>
</div>
</div>
<div class="flex items-center gap-4">
<div class="hidden sm:flex flex-col items-end mr-2">
<span class="text-sm font-bold text-foreground capitalize"><?php echo htmlspecialchars($username); ?></span>
<span class="text-xs font-semibold text-primary capitalize bg-primary/10 px-2 py-0.5 rounded-full"><?php echo htmlspecialchars($role); ?></span>
</div>
<div class="h-9 w-9 rounded-full bg-muted border border-border flex items-center justify-center text-muted-foreground font-bold overflow-hidden">
<!-- User Avatar Placeholder -->
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
</div>
<div class="w-px h-6 bg-border mx-1"></div>
<a href="logout.php" class="text-sm font-medium text-muted-foreground hover:text-primary transition-colors flex items-center gap-1.5" title="Keluar">
<span class="hidden sm:inline">Keluar</span>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
</a>
</div>
</div>
</header>
<!-- Main Content -->
<main class="relative z-10 mx-auto w-full max-w-6xl px-6 py-12 lg:px-8 flex-1 overflow-y-auto">
<!-- Welcome Section -->
<div class="mb-12 max-w-3xl">
<div class="mb-3 inline-flex items-center gap-2 rounded-full bg-white px-3 py-1 text-xs font-semibold text-primary ring-1 ring-border shadow-sm">
<span class="h-1.5 w-1.5 rounded-full bg-primary animate-pulse"></span>
Sistem Aktif
</div>
<h2 class="font-heading text-3xl sm:text-4xl font-extrabold tracking-tight text-foreground mb-3">
Halo, <span class="text-primary capitalize"><?php echo htmlspecialchars($username); ?></span> 👋
</h2>
<p class="text-base text-muted-foreground leading-relaxed">
Pilih modul analisis spasial yang ingin Anda kerjakan hari ini. Sistem menyediakan berbagai perangkat untuk pemetaan, digitasi, dan analisis data kemiskinan terpadu.
</p>
</div>
<!-- TAHAP 3: Buka Peta Analisis Kemiskinan Banner -->
<div class="mb-8 overflow-hidden rounded-3xl border border-rose-200/50 bg-gradient-to-r from-rose-500 via-primary to-rose-700 shadow-xl shadow-primary/20 relative">
<div class="absolute inset-0 bg-[url('assets/img/map-bg.png')] opacity-10 bg-cover bg-center mix-blend-overlay"></div>
<div class="absolute -right-20 -top-20 h-64 w-64 rounded-full bg-white/10 blur-3xl"></div>
<div class="relative z-10 flex flex-col md:flex-row items-center justify-between p-6 md:p-8 gap-6">
<div class="text-white text-center md:text-left">
<h2 class="text-2xl md:text-3xl font-extrabold font-heading mb-2 flex items-center gap-2 justify-center md:justify-start">
<svg viewBox="0 0 24 24" fill="none" class="h-7 w-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s-8-4-8-10V5l8-3 8 3v7c0 6-8 10-8 10z"></path></svg>
Aplikasi Kemiskinan Privat
</h2>
<p class="text-rose-100 font-medium text-sm md:text-base max-w-xl">
Akses eksklusif Admin & Petugas Lapangan. Kelola data spasial penduduk miskin, evaluasi program bantuan, dan update wilayah.
</p>
</div>
<a href="index.php?modul=bantuan" class="flex-shrink-0 group flex items-center justify-center gap-2 rounded-2xl bg-white text-primary px-8 py-4 font-bold shadow-lg transition-all hover:bg-rose-50 hover:scale-105 hover:shadow-xl active:scale-95 text-lg">
Buka Peta Analisis Kemiskinan
<svg viewBox="0 0 24 24" fill="none" class="h-5 w-5 transition-transform group-hover:translate-x-1" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</a>
</div>
</div>
<!-- Modules Grid -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<!-- Module 1: SPBU -->
<a href="index.php?modul=spbu" class="group relative flex flex-col justify-between rounded-3xl border border-white/60 bg-white/70 p-6 shadow-sm backdrop-blur-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-[0_20px_40px_-15px_rgba(225,29,72,0.15)] hover:border-primary/30">
<div class="mb-6 flex items-center justify-between">
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-orange-100 text-orange-600 transition-transform group-hover:scale-110 group-hover:rotate-3 shadow-inner">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 22v-8c0-1.1.9-2 2-2h4c1.1 0 2 .9 2 2v8"/><path d="M11 22H3"/><path d="M14 9V4a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v5"/><path d="M14 13h2a2 2 0 0 1 2 2v2a2 2 0 0 0 2 2h0a2 2 0 0 0 2-2V9.83a2 2 0 0 0-.59-1.42L18 5"/></svg>
</div>
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-muted/50 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 group-hover:bg-primary/10 group-hover:text-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</div>
</div>
<div>
<h3 class="font-heading text-lg font-bold text-foreground mb-2 group-hover:text-primary transition-colors">Pemetaan SPBU</h3>
<p class="text-sm text-muted-foreground leading-relaxed">
Kelola titik lokasi SPBU, pantau status operasional 24 jam, dan update informasi layanan bahan bakar.
</p>
</div>
</a>
<!-- Module 2: Bantuan -->
<a href="index.php?modul=bantuan" class="group relative flex flex-col justify-between rounded-3xl border border-white/60 bg-white/70 p-6 shadow-sm backdrop-blur-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-[0_20px_40px_-15px_rgba(225,29,72,0.15)] hover:border-primary/30">
<div class="mb-6 flex items-center justify-between">
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-emerald-100 text-emerald-600 transition-transform group-hover:scale-110 group-hover:rotate-3 shadow-inner">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
</div>
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-muted/50 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 group-hover:bg-primary/10 group-hover:text-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</div>
</div>
<div>
<h3 class="font-heading text-lg font-bold text-foreground mb-2 group-hover:text-primary transition-colors">Analisis Bantuan</h3>
<p class="text-sm text-muted-foreground leading-relaxed">
Data spasial penerima bantuan kemiskinan, upload dokumentasi foto rumah dan KK secara terpadu.
</p>
</div>
</a>
<!-- Module 3: Tematik -->
<a href="index.php?modul=tematik" class="group relative flex flex-col justify-between rounded-3xl border border-white/60 bg-white/70 p-6 shadow-sm backdrop-blur-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-[0_20px_40px_-15px_rgba(225,29,72,0.15)] hover:border-primary/30">
<div class="mb-6 flex items-center justify-between">
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-blue-100 text-blue-600 transition-transform group-hover:scale-110 group-hover:rotate-3 shadow-inner">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="3 6 9 3 15 6 21 3 21 18 15 21 9 18 3 21"></polygon><line x1="9" y1="3" x2="9" y2="18"></line><line x1="15" y1="6" x2="15" y2="21"></line></svg>
</div>
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-muted/50 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 group-hover:bg-primary/10 group-hover:text-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</div>
</div>
<div>
<h3 class="font-heading text-lg font-bold text-foreground mb-2 group-hover:text-primary transition-colors">Peta Tematik</h3>
<p class="text-sm text-muted-foreground leading-relaxed">
Visualisasi sebaran wilayah rawan bencana (Banjir, Longsor, Kebakaran) melalui layer GeoJSON yang interaktif.
</p>
</div>
</a>
<!-- Module 4: Digitasi -->
<a href="index.php?modul=digitasi" class="group relative flex flex-col justify-between rounded-3xl border border-white/60 bg-white/70 p-6 shadow-sm backdrop-blur-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-[0_20px_40px_-15px_rgba(225,29,72,0.15)] hover:border-primary/30">
<div class="mb-6 flex items-center justify-between">
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-purple-100 text-purple-600 transition-transform group-hover:scale-110 group-hover:rotate-3 shadow-inner">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>
</div>
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-muted/50 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 group-hover:bg-primary/10 group-hover:text-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</div>
</div>
<div>
<h3 class="font-heading text-lg font-bold text-foreground mb-2 group-hover:text-primary transition-colors">Digitasi Lahan</h3>
<p class="text-sm text-muted-foreground leading-relaxed">
Fitur editing geometri spasial. Buat dan edit batas area (polygon) langsung di atas peta untuk pemetaan aset.
</p>
</div>
</a>
<!-- Module 5: Utuh -->
<a href="index.php?modul=utuh" class="group relative flex flex-col justify-between rounded-3xl border border-white/60 bg-white/70 p-6 shadow-sm backdrop-blur-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-[0_20px_40px_-15px_rgba(225,29,72,0.15)] hover:border-primary/30 sm:col-span-2 lg:col-span-1">
<div class="mb-6 flex items-center justify-between">
<div class="flex h-12 w-12 items-center justify-center rounded-2xl bg-primary/10 text-primary transition-transform group-hover:scale-110 group-hover:rotate-3 shadow-inner">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
</div>
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-muted/50 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 group-hover:bg-primary/10 group-hover:text-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</div>
</div>
<div>
<h3 class="font-heading text-lg font-bold text-foreground mb-2 group-hover:text-primary transition-colors">WebGIS Terpadu</h3>
<p class="text-sm text-muted-foreground leading-relaxed">
Tampilan peta keseluruhan yang menggabungkan semua layer dan modul secara bersamaan.
</p>
</div>
</a>
</div>
<?php if ($role === 'admin'): ?>
<?php
include 'koneksi.php';
// Ambil data petugas pending
$stmtPending = $pdo->query("SELECT id, username, email, nama_lengkap, created_at FROM users WHERE role = 'petugas' AND status = 'pending'");
$pendingUsers = $stmtPending->fetchAll();
// Ambil semua data pengguna untuk manajemen
$stmtAll = $pdo->query("SELECT id, username, email, nama_lengkap, role, status, created_at FROM users ORDER BY created_at DESC");
$allUsers = $stmtAll->fetchAll();
// Ambil pengaturan show_demo_accounts
$stmtSet = $pdo->query("SELECT setting_value FROM settings WHERE setting_key = 'show_demo_accounts'");
$showDemo = '0';
if ($r = $stmtSet->fetch()) $showDemo = $r['setting_value'];
?>
<!-- Admin Dashboard Section -->
<div class="mt-12 space-y-8">
<h2 class="font-heading text-2xl font-bold text-foreground">Panel Administrator</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Approval Panel -->
<div class="rounded-3xl border border-white/60 bg-white/70 p-6 shadow-sm backdrop-blur-xl">
<div class="mb-4 flex items-center justify-between">
<h3 class="font-heading text-lg font-bold text-foreground">Approval Petugas Lapangan</h3>
<span class="inline-flex items-center justify-center rounded-full bg-rose-100 px-2.5 py-0.5 text-xs font-bold text-rose-700">
<?= count($pendingUsers) ?> Menunggu
</span>
</div>
<?php if (count($pendingUsers) > 0): ?>
<div class="overflow-x-auto rounded-xl border border-border">
<table class="w-full text-left text-sm text-muted-foreground">
<thead class="bg-muted/50 text-xs uppercase text-foreground">
<tr>
<th class="px-4 py-3">Nama Lengkap</th>
<th class="px-4 py-3">Email</th>
<th class="px-4 py-3 text-right">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<?php foreach ($pendingUsers as $u): ?>
<tr class="hover:bg-muted/30 transition-colors" id="row-<?= $u['id'] ?>">
<td class="px-4 py-3 font-medium text-foreground"><?= htmlspecialchars($u['nama_lengkap'] ?: $u['username']) ?></td>
<td class="px-4 py-3"><?= htmlspecialchars($u['email'] ?? '-') ?></td>
<td class="px-4 py-3 text-right">
<button onclick="handleAdminAction('approve_petugas', <?= $u['id'] ?>)" class="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-100 text-emerald-600 hover:bg-emerald-200 transition-colors" title="Setujui">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>
</button>
<button onclick="handleAdminAction('reject_petugas', <?= $u['id'] ?>)" class="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-rose-100 text-rose-600 hover:bg-rose-200 transition-colors ml-1" title="Tolak">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<div class="flex flex-col items-center justify-center py-8 text-center bg-muted/20 rounded-xl border border-dashed border-border">
<span class="text-3xl mb-2">🎉</span>
<p class="text-sm font-medium text-foreground">Tidak ada pendaftaran baru</p>
<p class="text-xs text-muted-foreground mt-1">Semua petugas telah diverifikasi.</p>
</div>
<?php endif; ?>
</div>
<!-- Settings Panel -->
<div class="rounded-3xl border border-white/60 bg-white/70 p-6 shadow-sm backdrop-blur-xl">
<h3 class="font-heading text-lg font-bold text-foreground mb-4">Pengaturan Sistem</h3>
<div class="flex items-center justify-between rounded-xl border border-border bg-white p-4">
<div>
<p class="font-semibold text-foreground text-sm">Tampilkan Tombol Akun Demo</p>
<p class="text-xs text-muted-foreground mt-0.5">Memunculkan helper login untuk demonstrasi aplikasi.</p>
</div>
<label class="relative inline-flex cursor-pointer items-center">
<input type="checkbox" id="toggleDemo" class="peer sr-only" <?= $showDemo === '1' ? 'checked' : '' ?> onchange="toggleSetting('show_demo_accounts', this.checked ? '1' : '0')">
<div class="h-6 w-11 rounded-full bg-slate-200 after:absolute after:top-[2px] after:left-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-primary peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary/20"></div>
</label>
</div>
</div>
</div>
<!-- Manajemen Pengguna Section -->
<div class="rounded-3xl border border-white/60 bg-white/70 p-6 shadow-sm backdrop-blur-xl mt-8">
<div class="mb-6 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h3 class="font-heading text-lg font-bold text-foreground flex items-center gap-2">
<span class="flex h-8 w-8 items-center justify-center rounded-lg bg-indigo-100 text-indigo-600">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
</span>
Manajemen Akun Terdaftar
</h3>
<p class="text-sm text-muted-foreground mt-1">Atur hak akses (role) dan status pengguna sistem.</p>
</div>
</div>
<div class="max-h-[500px] overflow-y-auto overflow-x-auto rounded-xl border border-border bg-white relative">
<table class="w-full text-left text-sm text-muted-foreground whitespace-nowrap">
<thead class="bg-muted/50 text-xs uppercase text-foreground sticky top-0 z-10 shadow-sm">
<tr>
<th class="px-4 py-3 font-semibold">Nama Lengkap</th>
<th class="px-4 py-3 font-semibold">Email / Username</th>
<th class="px-4 py-3 font-semibold">Role</th>
<th class="px-4 py-3 font-semibold">Status</th>
<th class="px-4 py-3 font-semibold">Terdaftar</th>
<th class="px-4 py-3 text-right font-semibold">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<?php foreach ($allUsers as $u): ?>
<tr class="hover:bg-muted/30 transition-colors" id="user-row-<?= $u['id'] ?>">
<td class="px-4 py-3 font-medium text-foreground">
<div class="flex items-center gap-3">
<div class="h-8 w-8 rounded-full bg-gradient-to-br from-slate-100 to-slate-200 border border-slate-300 flex items-center justify-center text-slate-600 font-bold">
<?= strtoupper(substr($u['nama_lengkap'] ?: $u['username'], 0, 1)) ?>
</div>
<?= htmlspecialchars($u['nama_lengkap'] ?: '-') ?>
</div>
</td>
<td class="px-4 py-3">
<div class="flex flex-col">
<span class="text-foreground"><?= htmlspecialchars($u['email'] ?: '-') ?></span>
<span class="text-xs text-muted-foreground">@<?= htmlspecialchars($u['username']) ?></span>
</div>
</td>
<td class="px-4 py-3 relative">
<?php if ($u['username'] === 'admin'): ?>
<span class="inline-flex h-8 items-center justify-center rounded-full bg-slate-100 px-3 text-xs font-bold tracking-wide text-slate-500 border border-slate-200 shadow-sm cursor-not-allowed">
Super Admin
</span>
<?php else: ?>
<div class="custom-select-wrapper relative inline-block min-w-[110px]" data-id="<?= $u['id'] ?>" data-type="role" data-value="<?= $u['role'] ?>">
<button type="button" class="select-btn flex items-center justify-between w-full rounded-full border border-slate-200 bg-slate-50/50 py-1.5 pl-3 pr-2.5 text-xs font-bold tracking-wide text-slate-700 shadow-sm transition-all hover:bg-slate-100 hover:border-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500/20">
<span class="select-text capitalize"><?= $u['role'] ?></span>
<svg class="h-3.5 w-3.5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg>
</button>
<div class="select-menu hidden absolute z-50 left-0 mt-1.5 w-full rounded-2xl border border-white bg-white/95 backdrop-blur-xl p-1.5 shadow-[0_10px_30px_rgba(0,0,0,0.08)]">
<div class="select-option cursor-pointer rounded-xl px-3 py-2 text-xs font-bold tracking-wide text-slate-600 hover:bg-indigo-50 hover:text-indigo-700 transition-colors mb-1" data-val="admin">Admin</div>
<div class="select-option cursor-pointer rounded-xl px-3 py-2 text-xs font-bold tracking-wide text-slate-600 hover:bg-indigo-50 hover:text-indigo-700 transition-colors mb-1" data-val="petugas">Petugas</div>
<div class="select-option cursor-pointer rounded-xl px-3 py-2 text-xs font-bold tracking-wide text-slate-600 hover:bg-indigo-50 hover:text-indigo-700 transition-colors" data-val="guest">Guest</div>
</div>
</div>
<?php endif; ?>
</td>
<td class="px-4 py-3 relative">
<?php if ($u['username'] === 'admin'): ?>
<span class="inline-flex h-8 items-center justify-center rounded-full bg-emerald-50 px-3 text-xs font-bold tracking-wide text-emerald-700 border border-emerald-200 shadow-sm cursor-not-allowed">
Active
</span>
<?php else: ?>
<div class="custom-select-wrapper relative inline-block min-w-[110px]" data-id="<?= $u['id'] ?>" data-type="status" data-value="<?= $u['status'] ?>">
<button type="button" class="select-btn flex items-center justify-between w-full rounded-full border py-1.5 pl-3 pr-2.5 text-xs font-bold tracking-wide shadow-sm transition-all focus:outline-none focus:ring-2 focus:ring-offset-1 <?= $u['status'] === 'active' ? 'border-emerald-200 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 focus:ring-emerald-500/30' : 'border-amber-200 bg-amber-50 text-amber-700 hover:bg-amber-100 focus:ring-amber-500/30' ?>">
<span class="select-text capitalize"><?= $u['status'] ?></span>
<svg class="h-3.5 w-3.5 <?= $u['status'] === 'active' ? 'text-emerald-500' : 'text-amber-500' ?>" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19 9l-7 7-7-7"></path></svg>
</button>
<div class="select-menu hidden absolute z-50 left-0 mt-1.5 w-full rounded-2xl border border-white bg-white/95 backdrop-blur-xl p-1.5 shadow-[0_10px_30px_rgba(0,0,0,0.08)]">
<div class="select-option cursor-pointer rounded-xl px-3 py-2 text-xs font-bold tracking-wide text-emerald-700 hover:bg-emerald-100 transition-colors mb-1" data-val="active">Active</div>
<div class="select-option cursor-pointer rounded-xl px-3 py-2 text-xs font-bold tracking-wide text-amber-700 hover:bg-amber-100 transition-colors" data-val="pending">Pending</div>
</div>
</div>
<?php endif; ?>
</td>
<td class="px-4 py-3 text-xs">
<?= date('d M Y, H:i', strtotime($u['created_at'])) ?>
</td>
<td class="px-4 py-3 text-right">
<?php if ($u['id'] != $_SESSION['user_id']): // Jangan tampilkan tombol hapus untuk diri sendiri ?>
<button onclick="deleteUser(<?= $u['id'] ?>)" class="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-rose-50 text-rose-600 hover:bg-rose-100 border border-rose-100 transition-colors" title="Hapus Akun">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"></path><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
</button>
<?php else: ?>
<span class="inline-flex h-8 items-center justify-center rounded-lg bg-slate-100 px-2 text-xs font-semibold text-slate-400 border border-slate-200 cursor-not-allowed">Anda</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Admin JS Scripts -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
function handleAdminAction(action, id) {
Swal.fire({
title: action === 'approve_petugas' ? 'Setujui Petugas?' : 'Tolak & Hapus?',
text: action === 'approve_petugas' ? 'Akun ini akan langsung aktif.' : 'Akun ini akan dihapus permanen.',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: action === 'approve_petugas' ? '#10b981' : '#e11d48',
cancelButtonColor: '#64748b',
confirmButtonText: 'Ya, Lanjutkan!',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
const formData = new FormData();
formData.append('action', action);
formData.append('id', id);
fetch('api_admin.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if(data.status === 'success') {
Swal.fire({
icon: 'success', title: 'Berhasil', showConfirmButton: false, timer: 1500
}).then(() => location.reload());
} else {
Swal.fire('Error', data.message, 'error');
}
}).catch(err => Swal.fire('Error', 'Terjadi kesalahan sistem.', 'error'));
}
});
}
function toggleSetting(key, val) {
const formData = new FormData();
formData.append('action', 'toggle_setting');
formData.append('key', key);
formData.append('value', val);
fetch('api_admin.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if(data.status === 'success') {
const Toast = Swal.mixin({
toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true
});
Toast.fire({ icon: 'success', title: 'Pengaturan disimpan' });
} else {
Swal.fire('Error', data.message, 'error');
}
});
}
document.addEventListener('click', e => {
if(!e.target.closest('.custom-select-wrapper') && !e.target.closest('.select-menu')) {
document.querySelectorAll('.select-menu:not(.hidden)').forEach(m => {
m.classList.add('hidden');
const orig = document.querySelector(`.custom-select-wrapper[data-id="${m.dataset.ownerId}"][data-type="${m.dataset.ownerType}"]`);
if(orig) orig.appendChild(m);
});
}
});
window.addEventListener('scroll', () => {
document.querySelectorAll('.select-menu:not(.hidden)').forEach(m => {
m.classList.add('hidden');
const orig = document.querySelector(`.custom-select-wrapper[data-id="${m.dataset.ownerId}"][data-type="${m.dataset.ownerType}"]`);
if(orig) orig.appendChild(m);
});
}, true);
window.addEventListener('resize', () => {
document.querySelectorAll('.select-menu:not(.hidden)').forEach(m => {
m.classList.add('hidden');
const orig = document.querySelector(`.custom-select-wrapper[data-id="${m.dataset.ownerId}"][data-type="${m.dataset.ownerType}"]`);
if(orig) orig.appendChild(m);
});
});
document.querySelectorAll('.custom-select-wrapper').forEach(wrapper => {
const btn = wrapper.querySelector('.select-btn');
const menu = wrapper.querySelector('.select-menu');
const textSpan = wrapper.querySelector('.select-text');
const type = wrapper.dataset.type;
const id = wrapper.dataset.id;
btn.addEventListener('click', (e) => {
e.stopPropagation();
// Hide other open menus and return them to their wrappers
document.querySelectorAll('.select-menu:not(.hidden)').forEach(m => {
if(m !== menu) {
m.classList.add('hidden');
const orig = document.querySelector(`.custom-select-wrapper[data-id="${m.dataset.ownerId}"][data-type="${m.dataset.ownerType}"]`);
if(orig) orig.appendChild(m);
}
});
if (menu.classList.contains('hidden')) {
menu.classList.remove('hidden');
// Pindahkan ke body agar tidak terpotong oleh overflow tabel atau terjebak backdrop-blur
menu.dataset.ownerId = id;
menu.dataset.ownerType = type;
document.body.appendChild(menu);
// Hitung posisi absolut terhadap dokumen
const rect = btn.getBoundingClientRect();
menu.style.position = 'absolute';
menu.style.top = (rect.bottom + window.scrollY + 4) + 'px';
menu.style.left = (rect.left + window.scrollX) + 'px';
menu.style.width = rect.width + 'px';
menu.style.zIndex = '99999';
} else {
menu.classList.add('hidden');
wrapper.appendChild(menu);
}
});
menu.querySelectorAll('.select-option').forEach(opt => {
opt.addEventListener('click', (e) => {
e.stopPropagation();
const val = opt.dataset.val;
wrapper.dataset.value = val;
textSpan.textContent = val;
// Kembalikan ke wrapper
menu.classList.add('hidden');
wrapper.appendChild(menu);
if(type === 'status') {
const icon = btn.querySelector('svg');
if(val === 'active') {
btn.className = "select-btn flex items-center justify-between w-full rounded-full border py-1.5 pl-3 pr-2.5 text-xs font-bold tracking-wide shadow-sm transition-all focus:outline-none focus:ring-2 focus:ring-offset-1 border-emerald-200 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 focus:ring-emerald-500/30";
icon.className = "h-3.5 w-3.5 text-emerald-500";
} else {
btn.className = "select-btn flex items-center justify-between w-full rounded-full border py-1.5 pl-3 pr-2.5 text-xs font-bold tracking-wide shadow-sm transition-all focus:outline-none focus:ring-2 focus:ring-offset-1 border-amber-200 bg-amber-50 text-amber-700 hover:bg-amber-100 focus:ring-amber-500/30";
icon.className = "h-3.5 w-3.5 text-amber-500";
}
}
updateUserCustom(id);
});
});
});
function updateUserCustom(id) {
const roleWrapper = document.querySelector(`.custom-select-wrapper[data-id="${id}"][data-type="role"]`);
const statusWrapper = document.querySelector(`.custom-select-wrapper[data-id="${id}"][data-type="status"]`);
const role = roleWrapper ? roleWrapper.dataset.value : '';
const status = statusWrapper ? statusWrapper.dataset.value : '';
const formData = new FormData();
formData.append('action', 'update_user');
formData.append('id', id);
formData.append('role', role);
formData.append('status', status);
fetch('api_admin.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if(data.status === 'success') {
const Toast = Swal.mixin({
toast: true, position: 'top-end', showConfirmButton: false, timer: 2000, timerProgressBar: true
});
Toast.fire({ icon: 'success', title: 'Akun berhasil diperbarui' });
} else {
Swal.fire('Error', data.message, 'error').then(() => location.reload());
}
}).catch(err => Swal.fire('Error', 'Terjadi kesalahan jaringan.', 'error'));
}
function deleteUser(id) {
Swal.fire({
title: 'Hapus Akun?',
text: 'Akun ini akan dihapus permanen beserta aksesnya.',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#e11d48',
cancelButtonColor: '#64748b',
confirmButtonText: 'Ya, Hapus!',
cancelButtonText: 'Batal'
}).then((result) => {
if (result.isConfirmed) {
const formData = new FormData();
formData.append('action', 'delete_user');
formData.append('id', id);
fetch('api_admin.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if(data.status === 'success') {
const row = document.getElementById('user-row-' + id);
if (row) row.remove();
Swal.fire({
icon: 'success', title: 'Dihapus', text: 'Akun berhasil dihapus.', showConfirmButton: false, timer: 1500
});
} else {
Swal.fire('Error', data.message, 'error');
}
}).catch(err => Swal.fire('Error', 'Terjadi kesalahan sistem.', 'error'));
}
});
}
</script>
<?php endif; ?>
</main>
<!-- Footer -->
<footer class="relative z-10 w-full mt-auto py-8">
<div class="mx-auto max-w-6xl px-6 text-center lg:px-8">
<p class="text-sm text-muted-foreground font-medium">
&copy; <?php echo date('Y'); ?> WebGIS Geospasial &middot; Kementerian Analisis Spasial
</p>
</div>
</footer>
</body>
</html>