<?php require_once __DIR__ . "/../includes/db.php";
require_once __DIR__ . "/../includes/auth.php";
date_default_timezone_set("Asia/Makassar");
if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}
if (!function_exists("esc")) {
    function esc($value)
    {
        return htmlspecialchars((string) $value, ENT_QUOTES, "UTF-8");
    }
}
/* ========================================================= KONFIGURASI ========================================================= */ $act =
    $_GET["a"] ?? "list";
if ($act === "edit") {
    $act = "form";
}
if ($act === "del") {
    $act = "delete";
}
$allowedActions = ["list", "form", "save", "delete"];
if (!in_array($act, $allowedActions, true)) {
    $act = "list";
}
$uploadDir = __DIR__ . "/../uploads";
$uploadUrl = "../uploads";
/* ========================================================= TOKEN KEAMANAN ========================================================= */ if (
    empty($_SESSION["csrf_potensi"])
) {
    try {
        $_SESSION["csrf_potensi"] = bin2hex(random_bytes(32));
    } catch (Throwable $e) {
        $_SESSION["csrf_potensi"] = hash(
            "sha256",
            uniqid("", true) . microtime(true),
        );
    }
}
$csrfToken = $_SESSION["csrf_potensi"];
/* ========================================================= DEFINISI KATEGORI ========================================================= */ $kategoriDef = [
    "pertanian" => [
        "label" => "Pertanian",
        "color" => "#198754",
        "background" => "#e8f6ed",
        "icon" => "ri-leaf-fill",
        "keywords" => ["pertanian", "padi", "sawah", "gabah", "beras", "jagung", "kedelai", "kacang", "ubi", "singkong", "talas", "tanaman pangan"],
    ],
    "hortikultura" => [
        "label" => "Hortikultura",
        "color" => "#2f9e44",
        "background" => "#edf9ef",
        "icon" => "ri-seedling-fill",
        "keywords" => ["hortikultura", "hurtikultura", "holtikultura", "cabai", "cabe", "tomat", "terong", "bayam", "kangkung", "sayur", "sayuran", "melon", "semangka"],
    ],
    "perkebunan" => [
        "label" => "Perkebunan",
        "color" => "#14833b",
        "background" => "#e7f5ea",
        "icon" => "ri-tree-fill",
        "keywords" => ["perkebunan", "kebun", "kelapa", "kopi", "kakao", "coklat", "cengkeh", "merica", "lada", "nilam", "sawit", "aren", "durian", "pisang"],
    ],
    "peternakan" => [
        "label" => "Peternakan",
        "color" => "#388e3c",
        "background" => "#eef7ee",
        "icon" => "ri-bear-smile-fill",
        "keywords" => ["peternakan", "ternak", "sapi", "kambing", "ayam", "bebek", "itik", "kerbau"],
    ],
    "perikanan" => [
        "label" => "Perikanan",
        "color" => "#1687c5",
        "background" => "#e9f5fb",
        "icon" => "ri-sailboat-fill",
        "keywords" => ["perikanan", "ikan", "lele", "nila", "ikan mas", "udang", "tambak", "kolam", "budidaya ikan"],
    ],
    "umkm" => [
        "label" => "UMKM",
        "color" => "#c57a16",
        "background" => "#fff6e6",
        "icon" => "ri-store-2-fill",
        "keywords" => ["umkm", "usaha", "ekonomi kreatif", "kerajinan", "anyaman", "tenun", "batik", "industri", "olahan", "kuliner", "makanan", "minuman", "warung", "produk lokal"],
    ],
    "pariwisata" => [
        "label" => "Pariwisata",
        "color" => "#4f6fb6",
        "background" => "#eef2fb",
        "icon" => "ri-camera-3-fill",
        "keywords" => ["pariwisata", "wisata", "destinasi", "air terjun", "pantai", "gunung", "budaya", "objek wisata"],
    ],
];
/* ========================================================= FUNGSI KATEGORI ========================================================= */ function potensi_normalize_text(
    $value
) {
    $text = trim((string) $value);
    if (function_exists("mb_strtolower")) {
        $text = mb_strtolower($text, "UTF-8");
    } else {
        $text = strtolower($text);
    }
    $normalized = preg_replace("/[^\p{L}\p{N}\s]+/u", " ", $text);
    if ($normalized === null) {
        $normalized = preg_replace("/[^a-z0-9\s]+/i", " ", $text);
    }
    return trim(preg_replace("/\s+/u", " ", (string) $normalized));
}
function potensi_kategori_pertama($kategoriDef)
{
    foreach ($kategoriDef as $key => $value) {
        return (string) $key;
    }
    return "tanaman";
}
function potensi_normalize_kategori($kategori, $title, $kategoriDef)
{
    $raw = potensi_normalize_text($kategori);
    $alias = [
        "tanaman" => "pertanian",
        "tanaman pangan" => "pertanian",
        "pertanian" => "pertanian",
        "hortikultura" => "hortikultura",
        "hurtikultura" => "hortikultura",
        "holtikultura" => "hortikultura",
        "perkebunan" => "perkebunan",
        "pertanian perkebunan" => "pertanian",
        "pertanian dan perkebunan" => "pertanian",
        "peternakan" => "peternakan",
        "perikanan" => "perikanan",
        "peternakan perikanan" => "peternakan",
        "peternakan dan perikanan" => "peternakan",
        "umkm" => "umkm",
        "umkm ekonomi kreatif" => "umkm",
        "umkm dan ekonomi kreatif" => "umkm",
        "ekonomi kreatif" => "umkm",
        "pariwisata" => "pariwisata",
        "wisata" => "pariwisata",
    ];

    if ($raw !== "") {
        if (isset($kategoriDef[$raw])) {
            return $raw;
        }
        if (isset($alias[$raw]) && isset($kategoriDef[$alias[$raw]])) {
            return $alias[$raw];
        }
        foreach ($kategoriDef as $key => $def) {
            $label = potensi_normalize_text($def["label"] ?? "");
            if ($raw === $label) {
                return $key;
            }
        }
    }

    $judul = potensi_normalize_text($title);
    if ($judul !== "") {
        foreach ($kategoriDef as $key => $def) {
            foreach ($def["keywords"] ?? [] as $keyword) {
                $keyword = potensi_normalize_text($keyword);
                if (
                    $keyword !== "" &&
                    preg_match(
                        "/(^|\s)" . preg_quote($keyword, "/") . '(\s|$)/u',
                        $judul,
                    )
                ) {
                    return $key;
                }
            }
        }
    }

    return potensi_kategori_pertama($kategoriDef);
}
/* ========================================================= FUNGSI TAMPILAN ========================================================= */ function potensi_ringkasan(
    $description,
    $panjang = 130
) {
    $text = trim(preg_replace("/\s+/", " ", strip_tags((string) $description)));
    if ($text === "") {
        return "Tidak ada deskripsi.";
    }
    if (function_exists("mb_strlen")) {
        if (mb_strlen($text, "UTF-8") <= $panjang) {
            return $text;
        }
        return mb_substr($text, 0, $panjang, "UTF-8") . "...";
    }
    if (strlen($text) <= $panjang) {
        return $text;
    }
    return substr($text, 0, $panjang) . "...";
}
function potensi_inisial($title)
{
    $title = trim((string) $title);
    if ($title === "") {
        return "P";
    }
    if (function_exists("mb_substr")) {
        return strtoupper(mb_substr($title, 0, 1, "UTF-8"));
    }
    return strtoupper(substr($title, 0, 1));
}
function potensi_tanggal_indo($tanggal)
{
    if (!$tanggal) {
        return "-";
    }
    $waktu = strtotime($tanggal);
    if (!$waktu) {
        return "-";
    }
    $bulan = [
        1 => "Januari",
        2 => "Februari",
        3 => "Maret",
        4 => "April",
        5 => "Mei",
        6 => "Juni",
        7 => "Juli",
        8 => "Agustus",
        9 => "September",
        10 => "Oktober",
        11 => "November",
        12 => "Desember",
    ];
    return date("j", $waktu) .
        " " .
        $bulan[(int) date("n", $waktu)] .
        " " .
        date("Y", $waktu);
}
function potensi_nama_file($filename)
{
    return basename(trim((string) $filename));
}
/* ========================================================= FUNGSI UPLOAD ========================================================= */ function potensi_pesan_upload(
    $kode
) {
    $pesan = [
        UPLOAD_ERR_INI_SIZE => "Ukuran file melebihi batas server.",
        UPLOAD_ERR_FORM_SIZE => "Ukuran file melebihi batas formulir.",
        UPLOAD_ERR_PARTIAL => "File hanya berhasil diunggah sebagian.",
        UPLOAD_ERR_NO_FILE => "Tidak ada file yang dipilih.",
        UPLOAD_ERR_NO_TMP_DIR => "Folder sementara upload tidak tersedia.",
        UPLOAD_ERR_CANT_WRITE => "Server gagal menulis file.",
        UPLOAD_ERR_EXTENSION => "Upload dihentikan oleh ekstensi server.",
    ];
    return $pesan[$kode] ?? "Terjadi kesalahan ketika mengunggah file.";
}
function potensi_pastikan_folder($uploadDir, &$errorText)
{
    if (!is_dir($uploadDir)) {
        $dibuat = mkdir($uploadDir, 0755, true);
        if (!$dibuat && !is_dir($uploadDir)) {
            $errorText = "Folder uploads tidak dapat dibuat.";
            return false;
        }
    }
    if (!is_writable($uploadDir)) {
        $errorText = "Folder uploads tidak memiliki izin tulis.";
        return false;
    }
    return true;
}
function potensi_hapus_file($uploadDir, $filename)
{
    $filename = potensi_nama_file($filename);
    if ($filename === "") {
        return;
    }
    $lokasi =
        rtrim($uploadDir, DIRECTORY_SEPARATOR) .
        DIRECTORY_SEPARATOR .
        $filename;
    if (is_file($lokasi) && file_exists($lokasi)) {
        @unlink($lokasi);
    }
}
function potensi_upload_media($file, $jenis, $uploadDir, &$errorText)
{
    if (
        empty($file) ||
        !isset($file["error"]) ||
        $file["error"] === UPLOAD_ERR_NO_FILE
    ) {
        return null;
    }
    if ($file["error"] !== UPLOAD_ERR_OK) {
        $errorText =
            "Upload " .
            ($jenis === "image" ? "gambar" : "video") .
            " gagal: " .
            potensi_pesan_upload($file["error"]);
        return false;
    }
    if (empty($file["tmp_name"]) || !is_uploaded_file($file["tmp_name"])) {
        $errorText = "File upload tidak valid.";
        return false;
    }
    $maksimalUkuran = $jenis === "image" ? 5 * 1024 * 1024 : 50 * 1024 * 1024;
    if ((int) $file["size"] > $maksimalUkuran) {
        $errorText =
            $jenis === "image"
                ? "Ukuran gambar maksimal 5 MB."
                : "Ukuran video maksimal 50 MB.";
        return false;
    }
    $namaAsli = trim((string) ($file["name"] ?? ""));
    $extension = strtolower(pathinfo($namaAsli, PATHINFO_EXTENSION));
    $mimeType = "";
    if (class_exists("finfo")) {
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mimeType = (string) $finfo->file($file["tmp_name"]);
    } elseif (function_exists("mime_content_type")) {
        $mimeType = (string) mime_content_type($file["tmp_name"]);
    }
    if ($jenis === "image") {
        $extensionDiizinkan = ["jpg", "jpeg", "png", "webp", "gif"];
        $mimeDiizinkan = [
            "image/jpeg" => "jpg",
            "image/png" => "png",
            "image/webp" => "webp",
            "image/gif" => "gif",
        ];
        if (
            !in_array($extension, $extensionDiizinkan, true) ||
            @getimagesize($file["tmp_name"]) === false
        ) {
            $errorText = "Gambar harus berformat JPG, PNG, WEBP, atau GIF.";
            return false;
        }
        if ($mimeType !== "" && !isset($mimeDiizinkan[$mimeType])) {
            $errorText = "Tipe file gambar tidak valid.";
            return false;
        }
        if (isset($mimeDiizinkan[$mimeType])) {
            $extension = $mimeDiizinkan[$mimeType];
        }
        $prefix = "potensi_img";
    } else {
        $extensionDiizinkan = ["mp4", "webm", "mov", "avi", "mkv"];
        $mimeDiizinkan = [
            "video/mp4",
            "application/mp4",
            "video/webm",
            "video/quicktime",
            "video/x-msvideo",
            "video/avi",
            "video/x-matroska",
            "application/x-matroska",
            "application/octet-stream",
        ];
        if (!in_array($extension, $extensionDiizinkan, true)) {
            $errorText = "Video harus berformat MP4, WEBM, MOV, AVI, atau MKV.";
            return false;
        }
        if ($mimeType !== "" && !in_array($mimeType, $mimeDiizinkan, true)) {
            $errorText = "Tipe file video tidak valid.";
            return false;
        }
        $prefix = "potensi_vid";
    }
    if (!potensi_pastikan_folder($uploadDir, $errorText)) {
        return false;
    }
    try {
        $kodeAcak = bin2hex(random_bytes(8));
    } catch (Throwable $e) {
        $kodeAcak = str_replace(".", "", uniqid("", true));
    }
    $namaBaru =
        $prefix . "_" . date("YmdHis") . "_" . $kodeAcak . "." . $extension;
    $namaBaru = preg_replace("/[^a-zA-Z0-9._-]/", "", $namaBaru);
    $lokasiTujuan =
        rtrim($uploadDir, DIRECTORY_SEPARATOR) .
        DIRECTORY_SEPARATOR .
        $namaBaru;
    if (!move_uploaded_file($file["tmp_name"], $lokasiTujuan)) {
        $errorText = "File gagal dipindahkan ke folder uploads.";
        return false;
    }
    return $namaBaru;
}
/* ========================================================= AMBIL SATU DATA ========================================================= */ function potensi_cari_data($mysqli, $id)
{
    $id = (int) $id;
    if ($id <= 0) {
        return null;
    }

    $stmt = $mysqli->prepare(
        " SELECT id, title, description, image, video, created_at, kategori, dusun, jumlah, keterangan
          FROM potensi_desa WHERE id = ? LIMIT 1 ",
    );
    if (!$stmt) {
        return null;
    }

    $stmt->bind_param("i", $id);
    $stmt->execute();
    $stmt->store_result();
    if ($stmt->num_rows === 0) {
        $stmt->close();
        return null;
    }

    $resultId = 0;
    $title = "";
    $description = "";
    $image = "";
    $video = "";
    $createdAt = null;
    $kategori = "";
    $dusun = "";
    $jumlah = "";
    $keterangan = "";

    $stmt->bind_result(
        $resultId,
        $title,
        $description,
        $image,
        $video,
        $createdAt,
        $kategori,
        $dusun,
        $jumlah,
        $keterangan,
    );
    $stmt->fetch();
    $stmt->close();

    return [
        "id" => $resultId,
        "title" => $title,
        "description" => $description,
        "image" => $image,
        "video" => $video,
        "created_at" => $createdAt,
        "kategori" => $kategori,
        "dusun" => $dusun,
        "jumlah" => $jumlah,
        "keterangan" => $keterangan,
    ];
}
/* ========================================================= HAPUS DATA ========================================================= */ if (
    $act === "delete" &&
    $_SERVER["REQUEST_METHOD"] === "POST"
) {
    $id = (int) ($_POST["id"] ?? 0);
    $token = $_POST["csrf_token"] ?? "";
    if (!is_string($token) || !hash_equals($csrfToken, $token)) {
        header("Location: potensi.php?msg=csrf");
        exit();
    }
    if ($id <= 0) {
        header("Location: potensi.php?msg=notfound");
        exit();
    }
    $dataLama = potensi_cari_data($mysqli, $id);
    if (!$dataLama) {
        header("Location: potensi.php?msg=notfound");
        exit();
    }
    $stmt = $mysqli->prepare(" DELETE FROM potensi_desa WHERE id = ? ");
    if (!$stmt) {
        header("Location: potensi.php?msg=error");
        exit();
    }
    $stmt->bind_param("i", $id);
    $berhasil = $stmt->execute();
    $terhapus = $berhasil && $stmt->affected_rows > 0;
    $stmt->close();
    if ($terhapus) {
        potensi_hapus_file($uploadDir, $dataLama["image"] ?? "");
        potensi_hapus_file($uploadDir, $dataLama["video"] ?? "");
    }
    header("Location: potensi.php?msg=" . ($terhapus ? "deleted" : "error"));
    exit();
}
/* ========================================================= SIMPAN / UPDATE ========================================================= */ $errorText =
    "";
$edit = null;
if ($act === "save" && $_SERVER["REQUEST_METHOD"] === "POST") {
    $id = (int) ($_POST["id"] ?? 0);
    $token = $_POST["csrf_token"] ?? "";
    $title = trim((string) ($_POST["title"] ?? ""));
    $description = trim((string) ($_POST["description"] ?? ""));
    $dusun = trim((string) ($_POST["dusun"] ?? ""));
    $jumlah = trim((string) ($_POST["jumlah"] ?? ""));
    $keterangan = trim((string) ($_POST["keterangan"] ?? ""));
    $kategoriInput = trim((string) ($_POST["kategori"] ?? ""));
    $kategori = potensi_normalize_kategori(
        $kategoriInput,
        $title,
        $kategoriDef,
    );
    $hapusImage = isset($_POST["hapus_image"]) && $_POST["hapus_image"] === "1";
    $hapusVideo = isset($_POST["hapus_video"]) && $_POST["hapus_video"] === "1";
    $dataLama = null;
    $imageBaru = null;
    $videoBaru = null;
    try {
        if (!is_string($token) || !hash_equals($csrfToken, $token)) {
            throw new RuntimeException(
                "Token keamanan tidak valid. Muat ulang halaman.",
            );
        }
        if ($title === "") {
            throw new RuntimeException("Komoditas wajib diisi.");
        }
        if (function_exists("mb_strlen") && mb_strlen($title, "UTF-8") > 255) {
            throw new RuntimeException("Komoditas maksimal 255 karakter.");
        }
        if (!function_exists("mb_strlen") && strlen($title) > 255) {
            throw new RuntimeException("Komoditas maksimal 255 karakter.");
        }
        if ($dusun === "") {
            throw new RuntimeException("Nama dusun wajib diisi.");
        }
        if (function_exists("mb_strlen") && mb_strlen($dusun, "UTF-8") > 150) {
            throw new RuntimeException("Nama dusun maksimal 150 karakter.");
        }
        if (!function_exists("mb_strlen") && strlen($dusun) > 150) {
            throw new RuntimeException("Nama dusun maksimal 150 karakter.");
        }
        if ($jumlah === "") {
            throw new RuntimeException("Jumlah potensi wajib diisi.");
        }
        if ((function_exists("mb_strlen") ? mb_strlen($jumlah, "UTF-8") : strlen($jumlah)) > 100) {
            throw new RuntimeException("Jumlah maksimal 100 karakter.");
        }
        if ((function_exists("mb_strlen") ? mb_strlen($keterangan, "UTF-8") : strlen($keterangan)) > 255) {
            throw new RuntimeException("Keterangan maksimal 255 karakter.");
        }
        if (!isset($kategoriDef[$kategori])) {
            throw new RuntimeException("Kategori potensi tidak valid.");
        }
        if ($id > 0) {
            $dataLama = potensi_cari_data($mysqli, $id);
            if (!$dataLama) {
                throw new RuntimeException("Data potensi tidak ditemukan.");
            }
        }
        $imageBaru = potensi_upload_media(
            $_FILES["image"] ?? [],
            "image",
            $uploadDir,
            $errorText,
        );
        if ($imageBaru === false) {
            throw new RuntimeException($errorText);
        }
        $videoBaru = potensi_upload_media(
            $_FILES["video"] ?? [],
            "video",
            $uploadDir,
            $errorText,
        );
        if ($videoBaru === false) {
            throw new RuntimeException($errorText);
        }
        $imageLama = (string) ($dataLama["image"] ?? "");
        $videoLama = (string) ($dataLama["video"] ?? "");
        $imageFinal = $imageLama;
        $videoFinal = $videoLama;
        if ($hapusImage) {
            $imageFinal = "";
        }
        if ($hapusVideo) {
            $videoFinal = "";
        }
        if (is_string($imageBaru) && $imageBaru !== "") {
            $imageFinal = $imageBaru;
        }
        if (is_string($videoBaru) && $videoBaru !== "") {
            $videoFinal = $videoBaru;
        }
        if ($id > 0) {
            $stmt = $mysqli->prepare(
                " UPDATE potensi_desa
                  SET title = ?, description = ?, image = ?, video = ?, kategori = ?, dusun = ?, jumlah = ?, keterangan = ?
                  WHERE id = ? ",
            );
            if (!$stmt) {
                throw new RuntimeException(
                    "Query pembaruan gagal disiapkan: " . $mysqli->error,
                );
            }
            $stmt->bind_param(
                "ssssssssi",
                $title,
                $description,
                $imageFinal,
                $videoFinal,
                $kategori,
                $dusun,
                $jumlah,
                $keterangan,
                $id,
            );
            if (!$stmt->execute()) {
                $queryError = $stmt->error;
                $stmt->close();
                throw new RuntimeException(
                    "Data potensi gagal diperbarui: " . $queryError,
                );
            }
            $stmt->close();
            if ($imageLama !== "" && $imageLama !== $imageFinal) {
                potensi_hapus_file($uploadDir, $imageLama);
            }
            if ($videoLama !== "" && $videoLama !== $videoFinal) {
                potensi_hapus_file($uploadDir, $videoLama);
            }
            header("Location: potensi.php?msg=updated");
            exit();
        }
        $stmt = $mysqli->prepare(
            " INSERT INTO potensi_desa
              ( title, description, image, video, kategori, dusun, jumlah, keterangan )
              VALUES (?, ?, ?, ?, ?, ?, ?, ?) ",
        );
        if (!$stmt) {
            throw new RuntimeException(
                "Query penyimpanan gagal disiapkan: " . $mysqli->error,
            );
        }
        $stmt->bind_param(
            "ssssssss",
            $title,
            $description,
            $imageFinal,
            $videoFinal,
            $kategori,
            $dusun,
            $jumlah,
            $keterangan,
        );
        if (!$stmt->execute()) {
            $queryError = $stmt->error;
            $stmt->close();
            throw new RuntimeException(
                "Data potensi gagal disimpan: " . $queryError,
            );
        }
        $stmt->close();
        header("Location: potensi.php?msg=saved");
        exit();
    } catch (Throwable $e) {
        if (is_string($imageBaru) && $imageBaru !== "") {
            potensi_hapus_file($uploadDir, $imageBaru);
        }
        if (is_string($videoBaru) && $videoBaru !== "") {
            potensi_hapus_file($uploadDir, $videoBaru);
        }
        $errorText = $e->getMessage();
        $act = "form";
        $edit = [
            "id" => $id,
            "title" => $title,
            "description" => $description,
            "kategori" => $kategori,
            "dusun" => $dusun,
            "jumlah" => $jumlah,
            "keterangan" => $keterangan,
            "image" => $dataLama["image"] ?? "",
            "video" => $dataLama["video"] ?? "",
            "created_at" => $dataLama["created_at"] ?? "",
        ];
    }
}
/* ========================================================= DATA EDIT ========================================================= */ if (
    $act === "form" &&
    $edit === null
) {
    $editId = (int) ($_GET["id"] ?? ($_POST["id"] ?? 0));
    if ($editId > 0) {
        $edit = potensi_cari_data($mysqli, $editId);
        if (!$edit) {
            header("Location: potensi.php?msg=notfound");
            exit();
        }
    } else {
        $edit = [
            "id" => 0,
            "title" => "",
            "description" => "",
            "image" => "",
            "video" => "",
            "created_at" => "",
            "kategori" => potensi_kategori_pertama($kategoriDef),
            "dusun" => "",
            "jumlah" => "",
            "keterangan" => "",
        ];
    }
}
/* ========================================================= NORMALISASI HALAMAN ========================================================= */ if (
    !in_array($act, ["list", "form"], true)
) {
    $act = "list";
}
/* ========================================================= PENCARIAN DAN FILTER ========================================================= */ $q = trim(
    (string) ($_GET["q"] ?? "")
);
$kategoriFilter = trim((string) ($_GET["kategori"] ?? ""));
$where = [];
if ($q !== "") {
    $safeQ = $mysqli->real_escape_string($q);
    $where[] = "( title LIKE '%{$safeQ}%' OR description LIKE '%{$safeQ}%' OR kategori LIKE '%{$safeQ}%'
                 OR dusun LIKE '%{$safeQ}%' OR jumlah LIKE '%{$safeQ}%' OR keterangan LIKE '%{$safeQ}%' )";
}
if ($kategoriFilter !== "" && isset($kategoriDef[$kategoriFilter])) {
    $safeKategori = $mysqli->real_escape_string($kategoriFilter);
    $where[] = "kategori = '{$safeKategori}'";
}
$whereSql = $where ? "WHERE " . implode(" AND ", $where) : "";
$list = $mysqli->query(
    " SELECT id, title, description, image, video, created_at, kategori, dusun, jumlah, keterangan
      FROM potensi_desa {$whereSql} ORDER BY id DESC "
);
$totalPotensi = $list ? $list->num_rows : 0;
/* ========================================================= NILAI FORM ========================================================= */ $isEdit = !empty(
    $edit["id"]
);
$titleValue = $_POST["title"] ?? ($edit["title"] ?? "");
$descriptionValue = $_POST["description"] ?? ($edit["description"] ?? "");
$dusunValue = $_POST["dusun"] ?? ($edit["dusun"] ?? "");
$jumlahValue = $_POST["jumlah"] ?? ($edit["jumlah"] ?? "");
$keteranganValue = $_POST["keterangan"] ?? ($edit["keterangan"] ?? "");
$kategoriValue =
    $_POST["kategori"] ??
    ($edit["kategori"] ?? potensi_kategori_pertama($kategoriDef));
$kategoriValue = potensi_normalize_kategori(
    $kategoriValue,
    $titleValue,
    $kategoriDef
);
$imageValue = $edit["image"] ?? "";
$videoValue = $edit["video"] ?? "";
/* ========================================================= PESAN ========================================================= */ $msgKey =
    $_GET["msg"] ?? "";
$msgText =
    [
        "saved" => "Data potensi desa berhasil ditambahkan.",
        "updated" => "Data potensi desa berhasil diperbarui.",
        "deleted" => "Data potensi desa berhasil dihapus.",
        "notfound" => "Data potensi desa tidak ditemukan.",
        "csrf" => "Token keamanan tidak valid. Silakan ulangi proses.",
        "error" => "Terjadi kesalahan saat memproses data potensi desa.",
    ][$msgKey] ?? "";
$msgError = in_array($msgKey, ["notfound", "csrf", "error"], true);
include __DIR__ . "/../includes/header.php";
?> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" > <link href="https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.css" rel="stylesheet" > <style> :root { --pot-green-950: #002818; --pot-green-900: #003d22; --pot-green-800: #005530; --pot-green-700: #047a3d; --pot-green-600: #09a04f; --pot-green-500: #15c46a; --pot-green-400: #35e085; --pot-green-100: #dcfaee; --pot-green-50: #edfff5; --pot-blue: #2080e8; --pot-yellow: #f5b400; --pot-orange: #f26b1d; --pot-purple: #7956d8; --pot-red: #dc3545; --pot-ink: #0f1c16; --pot-muted: #5e6e67; --pot-line: #daeae1; --pot-surface: #ffffff; --pot-page: #f3faf6; --pot-radius: 16px; --pot-shadow: 0 8px 28px rgba(10, 40, 25, .10); } @keyframes pot-gradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } @keyframes pot-pulse { 0% { box-shadow: 0 0 0 0 rgba(53, 224, 133, .45); } 70% { box-shadow: 0 0 0 12px rgba(53, 224, 133, 0); } 100% { box-shadow: 0 0 0 0 rgba(53, 224, 133, 0); } } @keyframes pot-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } } * { box-sizing: border-box; } body { color: var(--pot-ink); background-color: var(--pot-page); background-image: radial-gradient( ellipse at 80% 0%, rgba(21, 196, 106, .06), transparent 50% ), radial-gradient( ellipse at 20% 100%, rgba(9, 160, 79, .04), transparent 50% ); font-family: 'Inter', 'Segoe UI', system-ui, sans-serif; } .pot-page { padding: 26px; } /* ========================================================= HEADER ========================================================= */ .pot-header { position: relative; overflow: hidden; margin-bottom: 22px; padding: 26px 30px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; color: #ffffff; background: linear-gradient( 135deg, var(--pot-green-950), var(--pot-green-800), var(--pot-green-900) ); background-size: 200% 100%; border-radius: var(--pot-radius); box-shadow: 0 14px 40px rgba(0, 60, 30, .28); animation: pot-gradient 8s ease infinite; } .pot-header::before, .pot-header::after { content: ""; position: absolute; border-radius: 50%; pointer-events: none; } .pot-header::before { width: 220px; height: 220px; top: -80px; right: -55px; background: radial-gradient( circle, rgba(53, 224, 133, .20), transparent 70% ); animation: pot-float 7s ease-in-out infinite; } .pot-header::after { width: 130px; height: 130px; bottom: -40px; left: 30%; background: radial-gradient( circle, rgba(245, 180, 0, .12), transparent 70% ); animation: pot-float 5s ease-in-out infinite reverse; } .pot-header-left, .pot-header-actions { position: relative; z-index: 1; } .pot-eyebrow { margin: 0 0 8px; display: inline-flex; align-items: center; gap: 8px; color: var(--pot-green-400); font-size: 11px; font-weight: 800; letter-spacing: .10em; text-transform: uppercase; } .pot-eyebrow::before { content: ""; width: 22px; height: 2px; background: linear-gradient( 90deg, var(--pot-green-400), var(--pot-green-500) ); border-radius: 2px; } .pot-header h2 { margin: 0; color: #ffffff; font-size: clamp(20px, 3vw, 27px); font-weight: 800; letter-spacing: -.03em; line-height: 1.2; } .pot-title-icon { margin-right: 8px; color: var(--pot-green-400); font-size: 27px; vertical-align: middle; filter: drop-shadow( 0 0 8px rgba(53, 224, 133, .5) ); } .pot-description { margin: 10px 0 0; display: inline-flex; align-items: center; gap: 8px; color: rgba(255, 255, 255, .75); font-size: 12px; font-weight: 600; } .pot-live-dot { width: 8px; height: 8px; flex-shrink: 0; background: var(--pot-green-400); border-radius: 50%; animation: pot-pulse 2s ease infinite; } .pot-header-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 9px; } .pot-clock { padding: 10px 15px; display: inline-flex; align-items: center; gap: 8px; color: #ffffff; background: rgba(255, 255, 255, .12); border: 1px solid rgba(255, 255, 255, .18); border-radius: 12px; font-size: 13px; font-weight: 700; backdrop-filter: blur(8px); } .pot-clock i { color: var(--pot-green-400); font-size: 18px; } /* ========================================================= PANEL ========================================================= */ .pot-panel { overflow: hidden; background: var(--pot-surface); border: 1px solid rgba(210, 232, 220, .85); border-radius: var(--pot-radius); box-shadow: var(--pot-shadow); } .pot-panel-head { padding: 18px 20px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 14px; background: radial-gradient( circle at top right, rgba(21, 196, 106, .08), transparent 34% ), #ffffff; border-bottom: 1px solid var(--pot-line); } .pot-panel-title { display: flex; align-items: center; gap: 12px; } .pot-panel-icon { width: 42px; height: 42px; flex-shrink: 0; display: grid; place-items: center; color: #ffffff; background: linear-gradient( 135deg, var(--pot-green-500), var(--pot-green-800) ); border-radius: 13px; box-shadow: 0 4px 12px rgba(9, 160, 79, .28); font-size: 20px; } .pot-panel-title h3 { margin: 0; color: var(--pot-ink); font-size: 16px; font-weight: 800; } .pot-panel-title p { margin: 3px 0 0; color: var(--pot-muted); font-size: 12px; font-weight: 600; } .pot-panel-body { padding: 20px; } /* ========================================================= ALERT ========================================================= */ .pot-alert { margin-bottom: 16px; padding: 13px 15px; display: flex; align-items: center; gap: 9px; color: var(--pot-green-800); background: var(--pot-green-50); border: 1px solid var(--pot-green-100); border-radius: 14px; box-shadow: var(--pot-shadow); font-size: 14px; font-weight: 800; } .pot-alert-error { color: #a73c00; background: #fff4eb; border-color: #ffd8bd; } /* ========================================================= BUTTON ========================================================= */ .pot-btn { padding: 10px 15px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; border: none; border-radius: 12px; cursor: pointer; text-decoration: none; font-family: inherit; font-size: 13px; font-weight: 800; line-height: 1; box-shadow: 0 8px 18px rgba(10, 40, 25, .12); transition: transform .2s ease, filter .2s ease; } .pot-btn:hover { transform: translateY(-2px); filter: brightness(.98); } .pot-btn-primary { color: #ffffff; background: linear-gradient( 135deg, var(--pot-green-500), var(--pot-green-800) ); } .pot-btn-light { color: #ffffff; background: rgba(255, 255, 255, .12); border: 1px solid rgba(255, 255, 255, .18); backdrop-filter: blur(8px); } .pot-btn-image { color: #ffffff; background: linear-gradient( 135deg, #60b4ff, var(--pot-blue) ); } .pot-btn-video { color: #ffffff; background: linear-gradient( 135deg, #9c7be8, var(--pot-purple) ); } .pot-btn-edit { color: var(--pot-ink); background: linear-gradient( 135deg, #ffd95c, var(--pot-yellow) ); } .pot-btn-delete { color: #ffffff; background: linear-gradient( 135deg, #ff9a52, var(--pot-orange) ); } .pot-btn-ghost { color: var(--pot-green-800); background: var(--pot-green-50); border: 1px solid var(--pot-green-100); } .pot-btn-small { padding: 8px 11px; border-radius: 10px; box-shadow: none; font-size: 12px; } /* ========================================================= TOOLBAR ========================================================= */ .pot-toolbar { margin-bottom: 16px; display: grid; grid-template-columns: minmax(240px, 1fr) 240px auto; gap: 10px; } .pot-search { position: relative; } .pot-search i { position: absolute; top: 50%; left: 14px; color: var(--pot-green-700); font-size: 18px; transform: translateY(-50%); } .pot-search input, .pot-filter { width: 100%; min-height: 44px; color: var(--pot-ink); background: #f8fffb; border: 1px solid var(--pot-line); border-radius: 14px; outline: none; font-family: inherit; font-size: 13px; font-weight: 700; } .pot-search input { padding: 12px 14px 12px 42px; } .pot-filter { padding: 10px 13px; } .pot-search input:focus, .pot-filter:focus { background: #ffffff; border-color: var(--pot-green-500); box-shadow: 0 0 0 4px rgba(21, 196, 106, .13); } .pot-stat { padding: 10px 14px; display: inline-flex; align-items: center; justify-content: center; gap: 7px; color: var(--pot-green-800); background: var(--pot-green-50); border: 1px solid var(--pot-green-100); border-radius: 999px; font-size: 13px; font-weight: 800; white-space: nowrap; } /* ========================================================= TABLE ========================================================= */ .pot-table-wrap { overflow-x: auto; background: #ffffff; border: 1px solid var(--pot-line); border-radius: 16px; } .pot-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 14px; } .pot-table th { padding: 14px 13px; color: var(--pot-green-900); background: linear-gradient( 180deg, var(--pot-green-50), #f8fffb ); border-bottom: 1px solid var(--pot-line); font-size: 12px; font-weight: 800; letter-spacing: .04em; text-align: left; text-transform: uppercase; white-space: nowrap; } .pot-table td { padding: 14px 13px; border-bottom: 1px solid #edf7f1; vertical-align: middle; } .pot-table tbody tr { transition: background .16s ease; } .pot-table tbody tr:hover { background: var(--pot-green-50); } .pot-table tbody tr:last-child td { border-bottom: none; } .pot-number { width: 36px; height: 36px; display: inline-grid; place-items: center; color: var(--pot-green-800); background: var(--pot-green-50); border: 1px solid var(--pot-green-100); border-radius: 11px; font-size: 12px; font-weight: 800; } .pot-title-wrap { min-width: 290px; display: flex; align-items: flex-start; gap: 11px; } .pot-avatar { width: 42px; height: 42px; flex-shrink: 0; display: grid; place-items: center; color: #ffffff; background: linear-gradient( 135deg, var(--pot-green-500), var(--pot-green-800) ); border-radius: 14px; box-shadow: 0 8px 16px rgba(9, 160, 79, .20); font-weight: 800; } .pot-title { color: var(--pot-ink); font-weight: 800; line-height: 1.45; } .pot-summary { max-width: 420px; margin-top: 4px; color: var(--pot-muted); font-size: 11px; line-height: 1.55; } .pot-category { padding: 7px 10px; display: inline-flex; align-items: center; gap: 6px; border-radius: 999px; font-size: 12px; font-weight: 800; white-space: nowrap; } .pot-dusun { min-width: 130px; padding: 7px 10px; display: inline-flex; align-items: center; gap: 6px; color: var(--pot-green-800); background: var(--pot-green-50); border: 1px solid var(--pot-green-100); border-radius: 999px; font-size: 12px; font-weight: 800; white-space: nowrap; } .pot-dusun i { font-size: 15px; } .pot-image { width: 120px; height: 80px; display: block; object-fit: cover; background: #eef8f2; border: 1px solid var(--pot-line); border-radius: 14px; box-shadow: 0 7px 18px rgba(0, 0, 0, .12); cursor: pointer; transition: transform .2s ease, box-shadow .2s ease; } .pot-image:hover { transform: scale(1.04); box-shadow: 0 12px 25px rgba(0, 0, 0, .18); } .pot-media-empty { width: 120px; height: 80px; display: grid; place-items: center; color: var(--pot-green-700); background: var(--pot-green-50); border: 1px solid var(--pot-green-100); border-radius: 14px; font-size: 28px; } .pot-video-card { width: 120px; height: 80px; position: relative; overflow: hidden; display: grid; place-items: center; color: #ffffff; background: linear-gradient( 135deg, #273142, #111827 ); border: none; border-radius: 14px; cursor: pointer; box-shadow: 0 7px 18px rgba(0, 0, 0, .15); transition: transform .2s ease, box-shadow .2s ease; } .pot-video-card:hover { transform: scale(1.04); box-shadow: 0 12px 25px rgba(0, 0, 0, .22); } .pot-video-card i { width: 42px; height: 42px; display: grid; place-items: center; background: rgba(255, 255, 255, .18); border: 1px solid rgba(255, 255, 255, .20); border-radius: 50%; font-size: 23px; } .pot-date { min-width: 155px; display: inline-flex; align-items: center; gap: 6px; color: var(--pot-blue); background: #eef8ff; padding: 7px 10px; border-radius: 999px; font-size: 12px; font-weight: 800; white-space: nowrap; } .pot-actions { min-width: 230px; display: flex; align-items: center; flex-wrap: wrap; gap: 7px; } .pot-delete-form { margin: 0; } .pot-empty { padding: 38px !important; color: var(--pot-muted); background: #fbfffd; font-weight: 600; text-align: center; } .pot-empty i { display: block; margin-bottom: 8px; color: var(--pot-green-600); font-size: 36px; } /* ========================================================= FORM ========================================================= */ .pot-form { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } .pot-field { display: flex; flex-direction: column; gap: 7px; } .pot-field-full { grid-column: 1 / -1; } .pot-field label { color: var(--pot-ink); font-size: 13px; font-weight: 800; } .pot-required { color: var(--pot-orange); } .pot-input { width: 100%; min-height: 44px; padding: 12px 13px; color: var(--pot-ink); background: #f8fffb; border: 1px solid var(--pot-line); border-radius: 13px; outline: none; font-family: inherit; font-size: 14px; font-weight: 600; transition: border-color .2s ease, background .2s ease, box-shadow .2s ease; } .pot-input:focus { background: #ffffff; border-color: var(--pot-green-500); box-shadow: 0 0 0 4px rgba(21, 196, 106, .13); } textarea.pot-input { min-height: 180px; resize: vertical; line-height: 1.7; } .pot-section { grid-column: 1 / -1; padding: 12px 14px; display: flex; align-items: center; gap: 9px; color: var(--pot-green-900); background: var(--pot-green-50); border: 1px solid var(--pot-green-100); border-radius: 13px; font-size: 13px; font-weight: 800; } .pot-section i { color: var(--pot-green-600); font-size: 18px; } .pot-upload-box { height: 100%; padding: 17px; background: #f8fffb; border: 1px dashed var(--pot-green-500); border-radius: 14px; } .pot-help { margin: 5px 0 0; color: var(--pot-muted); font-size: 11px; font-weight: 600; line-height: 1.55; } .pot-preview { width: 100%; max-width: 420px; height: 250px; display: block; margin-top: 14px; object-fit: cover; background: #ffffff; border: 1px solid var(--pot-line); border-radius: 15px; box-shadow: 0 9px 22px rgba(0, 0, 0, .12); } .pot-preview-placeholder { width: 100%; max-width: 420px; height: 250px; margin-top: 14px; display: grid; place-items: center; color: var(--pot-green-700); background: var(--pot-green-50); border: 1px solid var(--pot-green-100); border-radius: 15px; font-size: 54px; } .pot-current-video { margin-top: 14px; padding: 12px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; background: #ffffff; border: 1px solid var(--pot-line); border-radius: 14px; } .pot-video-info { min-width: 0; display: flex; align-items: center; gap: 10px; } .pot-video-icon { width: 42px; height: 42px; flex-shrink: 0; display: grid; place-items: center; color: #ffffff; background: linear-gradient( 135deg, #9c7be8, var(--pot-purple) ); border-radius: 12px; font-size: 21px; } .pot-video-name { max-width: 280px; overflow: hidden; color: var(--pot-ink); font-size: 12px; font-weight: 800; text-overflow: ellipsis; white-space: nowrap; } .pot-checkbox { margin-top: 11px; display: inline-flex; align-items: center; gap: 8px; color: var(--pot-red); font-size: 12px; font-weight: 800; } .pot-checkbox input { width: 16px; height: 16px; } .pot-note { grid-column: 1 / -1; padding: 12px 14px; display: flex; align-items: flex-start; gap: 9px; color: var(--pot-green-900); background: var(--pot-green-50); border: 1px solid var(--pot-green-100); border-radius: 13px; font-size: 12px; font-weight: 600; line-height: 1.6; } .pot-note i { margin-top: 1px; color: var(--pot-green-600); font-size: 18px; } .pot-form-actions { grid-column: 1 / -1; display: flex; align-items: center; flex-wrap: wrap; gap: 10px; } /* ========================================================= MODAL ========================================================= */ .pot-modal { position: fixed; z-index: 99999; inset: 0; padding: 24px; display: none; align-items: center; justify-content: center; background: rgba(0, 20, 12, .86); backdrop-filter: blur(8px); } .pot-modal.active { display: flex; } .pot-modal-content { position: relative; width: min(100%, 1000px); max-height: 90vh; padding: 14px; background: #ffffff; border-radius: 18px; box-shadow: 0 25px 70px rgba(0, 0, 0, .42); } .pot-modal-content img, .pot-modal-content video { width: 100%; max-height: calc(90vh - 28px); display: block; object-fit: contain; border-radius: 12px; } .pot-modal-content video { background: #000000; } .pot-modal-close { position: absolute; top: -13px; right: -13px; width: 40px; height: 40px; display: grid; place-items: center; color: #ffffff; background: var(--pot-orange); border: 3px solid #ffffff; border-radius: 50%; cursor: pointer; font-size: 21px; box-shadow: 0 8px 20px rgba(0, 0, 0, .25); } /* ========================================================= RESPONSIVE ========================================================= */ @media (max-width: 900px) { .pot-toolbar { grid-template-columns: 1fr 220px; } .pot-toolbar .pot-btn { grid-column: 1 / -1; } } @media (max-width: 768px) { .pot-page { padding: 16px; } .pot-header { padding: 20px; } .pot-header-actions { width: 100%; flex-direction: column; align-items: stretch; } .pot-clock, .pot-btn-light { width: 100%; justify-content: center; } .pot-toolbar { grid-template-columns: 1fr; } .pot-toolbar .pot-btn { grid-column: 1; } .pot-form { grid-template-columns: 1fr; } .pot-field-full, .pot-section, .pot-note, .pot-form-actions { grid-column: 1; } .pot-actions { min-width: 220px; } .pot-preview, .pot-preview-placeholder { height: 220px; } } </style> <div class="pot-page"> <?php if (
     $act === "list"
 ): ?> <div class="pot-header"> <div class="pot-header-left"> <p class="pot-eyebrow"> <i class="ri-landscape-fill"></i> Kekayaan dan Sumber Daya Desa </p> <h2> <i class="ri-seedling-fill pot-title-icon"></i> Potensi Desa </h2> <p class="pot-description"> <span class="pot-live-dot"></span> Kelola pertanian, perkebunan, UMKM, peternakan, dan potensi ekonomi desa </p> </div> <div class="pot-header-actions"> <span class="pot-clock"> <i class="ri-time-fill"></i> <span id="potLiveClock"> --.--.-- </span> <span style="color:rgba(255,255,255,.55)" > WITA </span> </span> <a class="pot-btn pot-btn-light" href="potensi.php?a=form" > <i class="ri-add-circle-fill"></i> Tambah Potensi </a> </div> </div> <?php if (
     $msgText
 ): ?> <div class="pot-alert <?= $msgError
     ? "pot-alert-error"
     : "" ?>" > <i class="<?= $msgError
    ? "ri-error-warning-fill"
    : "ri-checkbox-circle-fill" ?>"></i> <?= esc(
    $msgText
) ?> </div> <?php endif; ?> <div class="pot-panel"> <div class="pot-panel-head"> <div class="pot-panel-title"> <div class="pot-panel-icon"> <i class="ri-folder-chart-fill"></i> </div> <div> <h3>Daftar Potensi Desa</h3> <p> Data potensi desa yang telah didokumentasikan. </p> </div> </div> <div class="pot-stat"> <i class="ri-bar-chart-box-fill"></i> <?= (int) $totalPotensi ?> Potensi </div> </div> <div class="pot-panel-body"> <form method="get" action="potensi.php" class="pot-toolbar" id="potensiFilterForm" > <div class="pot-search"> <i class="ri-search-line"></i> <input type="text" name="q" value="<?= esc(
     $q,
 ) ?>" placeholder="Cari judul, dusun, deskripsi, atau kategori..." autocomplete="off" > </div> <select name="kategori" class="pot-filter" onchange="this.form.submit()" > <option value=""> Semua Kategori </option> <?php foreach (
    $kategoriDef
    as $key => $def
): ?> <option value="<?= esc($key) ?>" <?= $kategoriFilter === $key
    ? "selected"
    : "" ?> > <?= esc(
     $def["label"],
 ) ?> </option> <?php endforeach; ?> </select> <?php if (
     $q !== "" ||
     $kategoriFilter !== ""
 ): ?> <a class="pot-btn pot-btn-ghost" href="potensi.php" > <i class="ri-refresh-line"></i> Reset </a> <?php endif; ?> </form> <div class="pot-table-wrap"> <table class="pot-table"> <thead> <tr> <th>No</th> <th>Komoditas</th> <th>Kategori</th> <th>Dusun</th> <th>Jumlah</th> <th>Keterangan</th> <th>Gambar</th> <th>Video</th> <th>Dibuat</th> <th>Aksi</th> </tr> </thead> <tbody> <?php if (
     $totalPotensi === 0
 ): ?> <tr> <td colspan="10" class="pot-empty" > <i class="ri-inbox-2-fill"></i> <?= $q !==
     "" || $kategoriFilter !== ""
     ? "Data potensi yang dicari tidak ditemukan."
     : "Belum ada data potensi desa. Klik tombol Tambah Potensi untuk menambahkan data baru." ?> </td> </tr> <?php endif; ?> <?php if (
     $list
 ): ?> <?php $no = 1; ?> <?php while ($row = $list->fetch_assoc()): ?> <?php
 $kategoriKey = potensi_normalize_kategori(
     $row["kategori"] ?? "",
     $row["title"] ?? "",
     $kategoriDef,
 );
 $kategoriData =
     $kategoriDef[$kategoriKey] ??
     $kategoriDef[potensi_kategori_pertama($kategoriDef)];
 $imageName = potensi_nama_file($row["image"] ?? "");
 $videoName = potensi_nama_file($row["video"] ?? "");
 $imagePath = $imageName !== "" ? $uploadUrl . "/" . $imageName : "";
 $videoPath = $videoName !== "" ? $uploadUrl . "/" . $videoName : "";
 ?> <tr class="pot-data-row"> <td> <span class="pot-number"> <?= $no++ ?> </span> </td> <td> <div class="pot-title-wrap"> <div class="pot-avatar"> <?= esc(
     potensi_inisial($row["title"]),
 ) ?> </div> <div> <div class="pot-title"> <?= esc(
     $row["title"],
 ) ?> </div> <div class="pot-summary"> <?= esc(
     potensi_ringkasan($row["description"]),
 ) ?> </div> </div> </div> </td> <td> <span class="pot-category" style=" color:<?= esc(
     $kategoriData["color"],
 ) ?>; background:<?= esc(
    $kategoriData["background"]
) ?>; " > <i class="<?= esc($kategoriData["icon"]) ?>"></i> <?= esc(
    $kategoriData["label"]
) ?> </span> </td> <td> <?php $rowDusun = trim(
     (string) ($row["dusun"] ?? ""),
 ); ?> <?php if (
     $rowDusun !== ""
 ): ?> <span class="pot-dusun"><i class="ri-map-pin-2-fill"></i> <?= esc(
     $rowDusun,
 ) ?></span> <?php else: ?> <span class="pot-dusun"><i class="ri-map-pin-line"></i> Belum ditentukan</span> <?php endif; ?> </td>
<td><strong><?= esc((string) ($row["jumlah"] ?? "")) ?></strong></td>
<td><span class="pot-summary"><?= esc((string) ($row["keterangan"] ?? "")) ?></span></td>
<td> <?php if (
     $imagePath !== ""
 ): ?> <img src="<?= esc($imagePath) ?>" alt="<?= esc(
    $row["title"]
) ?>" class="pot-image pot-open-image" data-src="<?= esc(
    $imagePath
) ?>" data-title="<?= esc(
    $row["title"]
) ?>" loading="lazy" > <?php else: ?> <div class="pot-media-empty"> <i class="ri-image-line"></i> </div> <?php endif; ?> </td> <td> <?php if (
     $videoPath !== ""
 ): ?> <button type="button" class="pot-video-card pot-open-video" data-src="<?= esc(
     $videoPath,
 ) ?>" data-title="<?= esc(
    $row["title"]
) ?>" aria-label="Putar video" > <i class="ri-play-fill"></i> </button> <?php else: ?> <div class="pot-media-empty"> <i class="ri-video-off-line"></i> </div> <?php endif; ?> </td> <td> <span class="pot-date"> <i class="ri-calendar-check-fill"></i> <?= esc(
     potensi_tanggal_indo($row["created_at"]),
 ) ?> </span> </td> <td> <div class="pot-actions"> <?php if (
     $imagePath !== ""
 ): ?> <button type="button" class="pot-btn pot-btn-small pot-btn-image pot-open-image" data-src="<?= esc(
     $imagePath,
 ) ?>" data-title="<?= esc(
    $row["title"]
) ?>" > <i class="ri-image-fill"></i> Gambar </button> <?php endif; ?> <?php if (
     $videoPath !== ""
 ): ?> <button type="button" class="pot-btn pot-btn-small pot-btn-video pot-open-video" data-src="<?= esc(
     $videoPath,
 ) ?>" data-title="<?= esc(
    $row["title"]
) ?>" > <i class="ri-video-fill"></i> Video </button> <?php endif; ?> <a class="pot-btn pot-btn-small pot-btn-edit" href="potensi.php?a=form&id=<?= (int) $row[
     "id"
 ] ?>" > <i class="ri-edit-2-fill"></i> Edit </a> <form method="post" action="potensi.php?a=delete" class="pot-delete-form" onsubmit="return confirm('Yakin ingin menghapus data potensi ini? Gambar dan video terkait juga akan dihapus dan tidak dapat dikembalikan.');" > <input type="hidden" name="csrf_token" value="<?= esc(
    $csrfToken
) ?>" > <input type="hidden" name="id" value="<?= (int) $row[
    "id"
] ?>" > <button type="submit" class="pot-btn pot-btn-small pot-btn-delete" > <i class="ri-delete-bin-6-fill"></i> Hapus </button> </form> </div> </td> </tr> <?php endwhile; ?> <?php endif; ?> </tbody> </table> </div> </div> </div> <?php else: ?> <div class="pot-header"> <div class="pot-header-left"> <p class="pot-eyebrow"> <i class="<?= $isEdit
     ? "ri-edit-2-fill"
     : "ri-add-circle-fill" ?>"></i> <?= $isEdit
    ? "Edit Potensi Desa"
    : "Tambah Potensi Desa" ?> </p> <h2> <i class="ri-seedling-fill pot-title-icon"></i> <?= $isEdit
     ? "Edit Data Potensi"
     : "Tambah Data Potensi" ?> </h2> <p class="pot-description"> <span class="pot-live-dot"></span> <?= $isEdit
     ? "Perbarui komoditas, kategori, dusun, jumlah, keterangan, dan dokumentasi potensi desa."
     : "Tambahkan komoditas, kategori, dusun, jumlah, keterangan, dan dokumentasi potensi desa." ?> </p> </div> <div class="pot-header-actions"> <a class="pot-btn pot-btn-light" href="potensi.php" > <i class="ri-arrow-left-line"></i> Kembali </a> </div> </div> <?php if (
     $errorText
 ): ?> <div class="pot-alert pot-alert-error"> <i class="ri-error-warning-fill"></i> <?= esc(
     $errorText,
 ) ?> </div> <?php endif; ?> <div class="pot-panel"> <div class="pot-panel-head"> <div class="pot-panel-title"> <div class="pot-panel-icon"> <i class="<?= $isEdit
     ? "ri-edit-2-fill"
     : "ri-file-add-fill" ?>"></i> </div> <div> <h3> <?= $isEdit
    ? "Edit Data Potensi"
    : "Form Tambah Potensi" ?> </h3> <p> Komoditas, kategori, dusun, dan jumlah wajib diisi. Gambar dan video bersifat opsional. </p> </div> </div> </div> <div class="pot-panel-body"> <form method="post" action="potensi.php?a=save" enctype="multipart/form-data" class="pot-form" id="potensiForm" > <input type="hidden" name="csrf_token" value="<?= esc(
     $csrfToken,
 ) ?>" > <input type="hidden" name="id" value="<?= (int) ($edit["id"] ??
    0) ?>" > <div class="pot-section"> <i class="ri-file-text-fill"></i> Informasi Potensi </div> <div class="pot-field"> <label for="title"> Komoditas <span class="pot-required">*</span> </label> <input type="text" class="pot-input" id="title" name="title" value="<?= esc(
    $titleValue
) ?>" placeholder="Contoh: Padi" maxlength="255" required > </div> <div class="pot-field"> <label for="kategori"> Kategori <span class="pot-required">*</span> </label> <select class="pot-input" id="kategori" name="kategori" required > <?php foreach (
    $kategoriDef
    as $key => $def
): ?> <option value="<?= esc($key) ?>" <?= $kategoriValue === $key
    ? "selected"
    : "" ?> > <?= esc(
     $def["label"],
 ) ?> </option> <?php endforeach; ?> </select> </div> <div class="pot-field">
<label for="dusun"> Dusun <span class="pot-required">*</span> </label>
<input type="text" class="pot-input" id="dusun" name="dusun" value="<?= esc($dusunValue) ?>" placeholder="Contoh: Dusun I" maxlength="150" required >
<p class="pot-help">Masukkan nama dusun lokasi potensi.</p>
</div>
<div class="pot-field">
<label for="jumlah"> Jumlah <span class="pot-required">*</span> </label>
<input type="text" class="pot-input" id="jumlah" name="jumlah" value="<?= esc($jumlahValue) ?>" placeholder="Contoh: 15 Ha / 25 Ekor / 3 Kolam" maxlength="100" required >
<p class="pot-help">Tulis angka beserta satuannya agar tampil langsung pada halaman pengguna.</p>
</div>
<div class="pot-field pot-field-full">
<label for="keterangan"> Keterangan </label>
<input type="text" class="pot-input" id="keterangan" name="keterangan" value="<?= esc($keteranganValue) ?>" placeholder="Contoh: Lahan produktif / Ternak warga" maxlength="255" >
</div>
<div class="pot-field pot-field-full"> <label for="description"> Deskripsi Lengkap </label> <textarea class="pot-input" id="description" name="description" rows="8" placeholder="Jelaskan potensi desa secara lebih lengkap, manfaat, lokasi, proses pengelolaan, atau informasi lainnya..." ><?= esc(
    $descriptionValue
) ?></textarea> </div> <div class="pot-section"> <i class="ri-multimedia-fill"></i> Dokumentasi Media </div> <div class="pot-field"> <div class="pot-upload-box"> <label for="image"> <?= $isEdit
    ? "Ganti Gambar"
    : "Upload Gambar" ?> </label> <input type="file" class="pot-input" id="image" name="image" accept=".jpg,.jpeg,.png,.webp,.gif,image/jpeg,image/png,image/webp,image/gif" > <p class="pot-help"> Format JPG, PNG, WEBP, atau GIF. Ukuran maksimal 5 MB. <?= $isEdit
     ? "Kosongkan jika tidak ingin mengganti gambar."
     : "" ?> </p> <?php if (
     $imageValue !== ""
 ): ?> <img id="imagePreview" class="pot-preview" src="<?= esc(
     $uploadUrl . "/" . potensi_nama_file($imageValue),
 ) ?>" alt="Preview gambar" > <div id="imagePlaceholder" class="pot-preview-placeholder" style="display:none" > <i class="ri-image-line"></i> </div> <label class="pot-checkbox"> <input type="checkbox" name="hapus_image" value="1" id="hapusImage" > Hapus gambar lama </label> <?php else: ?> <div id="imagePlaceholder" class="pot-preview-placeholder" > <i class="ri-image-add-line"></i> </div> <img id="imagePreview" class="pot-preview" alt="Preview gambar" style="display:none" > <?php endif; ?> </div> </div> <div class="pot-field"> <div class="pot-upload-box"> <label for="video"> <?= $isEdit
     ? "Ganti Video"
     : "Upload Video" ?> </label> <input type="file" class="pot-input" id="video" name="video" accept=".mp4,.webm,.mov,.avi,.mkv,video/mp4,video/webm,video/quicktime,video/x-msvideo" > <p class="pot-help"> Format MP4, WEBM, MOV, AVI, atau MKV. Ukuran maksimal 50 MB. <?= $isEdit
     ? "Kosongkan jika tidak ingin mengganti video."
     : "" ?> </p> <div id="selectedVideo" class="pot-current-video" style="display:none" > <div class="pot-video-info"> <div class="pot-video-icon"> <i class="ri-video-fill"></i> </div> <div class="pot-video-name" id="selectedVideoName" ></div> </div> </div> <?php if (
     $videoValue !== ""
 ): ?> <div class="pot-current-video"> <div class="pot-video-info"> <div class="pot-video-icon"> <i class="ri-video-fill"></i> </div> <div class="pot-video-name"> <?= esc(
     potensi_nama_file($videoValue),
 ) ?> </div> </div> <button type="button" class="pot-btn pot-btn-small pot-btn-video pot-open-video" data-src="<?= esc(
     $uploadUrl . "/" . potensi_nama_file($videoValue),
 ) ?>" data-title="<?= esc(
    $titleValue
) ?>" > <i class="ri-play-fill"></i> Putar </button> </div> <label class="pot-checkbox"> <input type="checkbox" name="hapus_video" value="1" id="hapusVideo" > Hapus video lama </label> <?php endif; ?> </div> </div> <div class="pot-note"> <i class="ri-information-fill"></i> <span> Ketika gambar atau video baru diunggah saat proses edit, media lama akan dihapus setelah pembaruan berhasil. Menghapus data potensi juga akan menghapus file gambar dan video dari server. </span> </div> <div class="pot-form-actions"> <button type="submit" class="pot-btn pot-btn-primary" > <i class="ri-save-3-fill"></i> <?= $isEdit
     ? "Perbarui Data"
     : "Simpan Data" ?> </button> <a class="pot-btn pot-btn-ghost" href="potensi.php" > <i class="ri-close-circle-fill"></i> Batal </a> </div> </form> </div> </div> <?php endif; ?> </div> <div class="pot-modal" id="potImageModal" > <div class="pot-modal-content"> <button type="button" class="pot-modal-close" id="closePotImageModal" aria-label="Tutup" > <i class="ri-close-line"></i> </button> <img id="potModalImage" src="" alt="" > </div> </div> <div class="pot-modal" id="potVideoModal" > <div class="pot-modal-content"> <button type="button" class="pot-modal-close" id="closePotVideoModal" aria-label="Tutup" > <i class="ri-close-line"></i> </button> <video id="potModalVideo" controls preload="metadata" ></video> </div> </div> <script> (() => { const clockElement = document.getElementById( 'potLiveClock' ); const updateClock = () => { if (!clockElement) { return; } clockElement.textContent = new Intl.DateTimeFormat( 'id-ID', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, timeZone: 'Asia/Makassar' } ) .format(new Date()) .replaceAll(':', '.'); }; updateClock(); setInterval( updateClock, 1000 ); const searchInput = document.querySelector( '.pot-search input' ); let searchTimer = null; if (searchInput) { searchInput.addEventListener( 'input', function () { clearTimeout( searchTimer ); searchTimer = setTimeout( () => { this.form.submit(); }, 650 ); } ); } const imageInput = document.getElementById( 'image' ); const imagePreview = document.getElementById( 'imagePreview' ); const imagePlaceholder = document.getElementById( 'imagePlaceholder' ); const hapusImage = document.getElementById( 'hapusImage' ); if ( imageInput && imagePreview ) { imageInput.addEventListener( 'change', function () { const file = this.files && this.files.length > 0 ? this.files[0] : null; if (!file) { return; } const allowedTypes = [ 'image/jpeg', 'image/png', 'image/webp', 'image/gif' ]; if ( !allowedTypes.includes( file.type ) ) { alert( 'Gambar harus berformat JPG, PNG, WEBP, atau GIF.' ); this.value = ''; return; } if ( file.size > 5 * 1024 * 1024 ) { alert( 'Ukuran gambar maksimal 5 MB.' ); this.value = ''; return; } const reader = new FileReader(); reader.onload = function ( event ) { imagePreview.src = event.target.result; imagePreview.style.display = 'block'; imagePreview.style.opacity = '1'; if (imagePlaceholder) { imagePlaceholder.style.display = 'none'; } if (hapusImage) { hapusImage.checked = false; } }; reader.readAsDataURL( file ); } ); } if ( hapusImage && imagePreview ) { hapusImage.addEventListener( 'change', function () { imagePreview.style.opacity = this.checked ? '.3' : '1'; } ); } const videoInput = document.getElementById( 'video' ); const selectedVideo = document.getElementById( 'selectedVideo' ); const selectedVideoName = document.getElementById( 'selectedVideoName' ); const hapusVideo = document.getElementById( 'hapusVideo' ); if (videoInput) { videoInput.addEventListener( 'change', function () { const file = this.files && this.files.length > 0 ? this.files[0] : null; if (!file) { if (selectedVideo) { selectedVideo.style.display = 'none'; } return; } const extension = file.name .split('.') .pop() .toLowerCase(); const allowedExtensions = [ 'mp4', 'webm', 'mov', 'avi', 'mkv' ]; if ( !allowedExtensions.includes( extension ) ) { alert( 'Video harus berformat MP4, WEBM, MOV, AVI, atau MKV.' ); this.value = ''; return; } if ( file.size > 50 * 1024 * 1024 ) { alert( 'Ukuran video maksimal 50 MB.' ); this.value = ''; return; } if ( selectedVideo && selectedVideoName ) { selectedVideoName.textContent = file.name; selectedVideo.style.display = 'flex'; } if (hapusVideo) { hapusVideo.checked = false; } } ); } const imageModal = document.getElementById( 'potImageModal' ); const modalImage = document.getElementById( 'potModalImage' ); const closeImageButton = document.getElementById( 'closePotImageModal' ); const openImageModal = ( src, title ) => { if ( !imageModal || !modalImage ) { return; } modalImage.src = src; modalImage.alt = title || 'Gambar potensi desa'; imageModal.classList.add( 'active' ); document.body.style.overflow = 'hidden'; }; const closeImageModal = () => { if (!imageModal) { return; } imageModal.classList.remove( 'active' ); if (modalImage) { modalImage.src = ''; } document.body.style.overflow = ''; }; document .querySelectorAll( '.pot-open-image' ) .forEach((element) => { element.addEventListener( 'click', function () { openImageModal( this.dataset.src || '', this.dataset.title || '' ); } ); }); if (closeImageButton) { closeImageButton.addEventListener( 'click', closeImageModal ); } if (imageModal) { imageModal.addEventListener( 'click', function (event) { if ( event.target === imageModal ) { closeImageModal(); } } ); } const videoModal = document.getElementById( 'potVideoModal' ); const modalVideo = document.getElementById( 'potModalVideo' ); const closeVideoButton = document.getElementById( 'closePotVideoModal' ); const openVideoModal = ( src, title ) => { if ( !videoModal || !modalVideo ) { return; } modalVideo.src = src; modalVideo.setAttribute( 'aria-label', title || 'Video potensi desa' ); videoModal.classList.add( 'active' ); document.body.style.overflow = 'hidden'; modalVideo.load(); const playPromise = modalVideo.play(); if ( playPromise && typeof playPromise.catch === 'function' ) { playPromise.catch( () => {} ); } }; const closeVideoModal = () => { if (!videoModal) { return; } videoModal.classList.remove( 'active' ); if (modalVideo) { modalVideo.pause(); modalVideo.removeAttribute( 'src' ); modalVideo.load(); } document.body.style.overflow = ''; }; document .querySelectorAll( '.pot-open-video' ) .forEach((element) => { element.addEventListener( 'click', function () { openVideoModal( this.dataset.src || '', this.dataset.title || '' ); } ); }); if (closeVideoButton) { closeVideoButton.addEventListener( 'click', closeVideoModal ); } if (videoModal) { videoModal.addEventListener( 'click', function (event) { if ( event.target === videoModal ) { closeVideoModal(); } } ); } document.addEventListener( 'keydown', function (event) { if (event.key === 'Escape') { closeImageModal(); closeVideoModal(); } } ); })(); </script> <?php include __DIR__ .
     "/../includes/footer.php"; ?>
