610 lines
42 KiB
PHP
610 lines
42 KiB
PHP
<?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>
|
||
|
||
<!-- 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">
|
||
© <?php echo date('Y'); ?> WebGIS Geospasial · Kementerian Analisis Spasial
|
||
</p>
|
||
</div>
|
||
</footer>
|
||
|
||
</body>
|
||
</html>
|