diff --git a/archive_bootstrap.php b/archive_bootstrap.php
new file mode 100644
index 0000000..7e574eb
--- /dev/null
+++ b/archive_bootstrap.php
@@ -0,0 +1,527 @@
+ true,
+ "samesite" => "Lax",
+ "secure" => $secure,
+ ]);
+ session_start();
+}
+
+function project_meta(): array
+{
+ $projectName = $_SERVER["PROJECT_NAME"] ?? "KBRI Harare Archive";
+ $projectDescription = $_SERVER["PROJECT_DESCRIPTION"] ?? "Aplikasi arsip internal KBRI Harare untuk pencatatan dokumen, folder bertingkat, dan kontrol akses kerja.";
+ $projectImageUrl = $_SERVER["PROJECT_IMAGE_URL"] ?? "";
+
+ return [$projectName, $projectDescription, $projectImageUrl];
+}
+
+function archive_tree(): array
+{
+ return [
+ [
+ "label" => "INFORMASI NEGARA",
+ "children" => [
+ [
+ "label" => "Zimbabwe",
+ "children" => [
+ ["label" => "Keterangan Dasar"],
+ ["label" => "Informasi Penting"],
+ ["label" => "Catatan Peristiwa Penting"],
+ ],
+ ],
+ [
+ "label" => "Zambia",
+ "children" => [
+ ["label" => "Keterangan Dasar"],
+ ["label" => "Informasi Penting"],
+ ["label" => "Catatan Peristiwa Penting"],
+ ],
+ ],
+ ],
+ ],
+ [
+ "label" => "POLITIK",
+ "children" => [
+ [
+ "label" => "Nota Diplomatik",
+ "children" => [
+ ["label" => "Keluar"],
+ ["label" => "Masuk"],
+ ],
+ ],
+ ["label" => "Perjanjian & Kesepakatan"],
+ ["label" => "Hubungan Bilateral"],
+ ["label" => "Kebijakan Strategis"],
+ ],
+ ],
+ [
+ "label" => "PENSOSBUD",
+ "children" => [
+ ["label" => "Program Kerja Sama"],
+ ["label" => "Event Budaya"],
+ [
+ "label" => "Scholarship",
+ "children" => [
+ ["label" => "Permohonan"],
+ ["label" => "Persetujuan"],
+ ],
+ ],
+ ],
+ ],
+ [
+ "label" => "EKONOMI & PERDAGANGAN",
+ "children" => [
+ [
+ "label" => "Nota Kesepahaman",
+ "children" => [
+ ["label" => "Zimbabwe"],
+ ["label" => "Zambia"],
+ ],
+ ],
+ [
+ "label" => "Agreement Investasi",
+ "children" => [
+ ["label" => "Indonesia"],
+ ["label" => "Zimbabwe"],
+ ["label" => "Zambia"],
+ ],
+ ],
+ [
+ "label" => "Kerjasama Perusahaan",
+ "children" => [
+ ["label" => "Indonesia"],
+ ["label" => "Zimbabwe"],
+ ["label" => "Zambia"],
+ ],
+ ],
+ ],
+ ],
+ [
+ "label" => "KEKONSULERAN",
+ "children" => [
+ [
+ "label" => "Dokumen Perjalanan & Identitas",
+ "children" => [
+ ["label" => "Visa & Izin Tinggal"],
+ ["label" => "SPLP"],
+ ["label" => "Surat Keterangan"],
+ ["label" => "Pencatatan Sipil"],
+ ["label" => "Bantuan Hukum"],
+ ["label" => "Repatriasi Jenazah"],
+ ["label" => "Izin Diplomatik"],
+ ],
+ ],
+ ["label" => "Legalisasi Dokumen"],
+ ["label" => "Perlindungan WNI"],
+ ["label" => "Fasilitas Diplomatik"],
+ ],
+ ],
+ [
+ "label" => "KANSELERAI / HOC",
+ "children" => [
+ ["label" => "SOP"],
+ ["label" => "Kepegawaian"],
+ ["label" => "Perkantoran"],
+ ["label" => "Wisma Duta"],
+ ],
+ ],
+ [
+ "label" => "PID",
+ "children" => [
+ ["label" => "Contingency Plan"],
+ ["label" => "Komputerisasi"],
+ ["label" => "Pengamanan Terpadu"],
+ ],
+ ],
+ [
+ "label" => "ADMIN & INTERNAL",
+ "children" => [
+ ["label" => "Surat Edaran"],
+ ["label" => "Laporan Internal"],
+ ["label" => "Arsip Kepegawaian"],
+ ["label" => "Inventaris & Logistik"],
+ ],
+ ],
+ [
+ "label" => "Gallery",
+ "children" => [
+ ["label" => "File Penting"],
+ ["label" => "Photo Kegiatan"],
+ ["label" => "Video Dokumentasi & Lain-lain"],
+ ],
+ ],
+ ];
+}
+
+function flatten_tree(array $nodes, array $parents = []): array
+{
+ $flat = [];
+ foreach ($nodes as $node) {
+ $pathParts = [...$parents, $node["label"]];
+ $path = implode(" / ", $pathParts);
+ $mainMenu = $parents[0] ?? $node["label"];
+ $flat[] = [
+ "label" => $node["label"],
+ "path" => $path,
+ "main_menu" => $mainMenu,
+ "has_children" => !empty($node["children"]),
+ "depth" => count($parents),
+ ];
+ if (!empty($node["children"])) {
+ $flat = array_merge($flat, flatten_tree($node["children"], $pathParts));
+ }
+ }
+ return $flat;
+}
+
+function folder_lookup(): array
+{
+ static $lookup = null;
+ if ($lookup !== null) {
+ return $lookup;
+ }
+
+ $lookup = [];
+ foreach (flatten_tree(archive_tree()) as $item) {
+ $lookup[$item["path"]] = $item;
+ }
+
+ return $lookup;
+}
+
+function main_menu_options(): array
+{
+ return array_map(static fn(array $node): string => $node["label"], archive_tree());
+}
+
+function users_catalog(): array
+{
+ static $users = null;
+ if ($users !== null) {
+ return $users;
+ }
+
+ $defaultPassword = "Kbri2026!";
+ $hash = password_hash($defaultPassword, PASSWORD_DEFAULT);
+
+ $users = [
+ "super.admin1" => ["name" => "Super Admin 1", "role" => "super_admin", "unit" => "Pimpinan", "allowed_menus" => main_menu_options(), "password_hash" => $hash],
+ "super.admin2" => ["name" => "Super Admin 2", "role" => "super_admin", "unit" => "Pimpinan", "allowed_menus" => main_menu_options(), "password_hash" => $hash],
+ "politik.head" => ["name" => "Kabid Politik", "role" => "kepala_bagian", "unit" => "Politik", "allowed_menus" => ["INFORMASI NEGARA", "POLITIK"], "password_hash" => $hash],
+ "pensosbud.head" => ["name" => "Kabid Pensosbud", "role" => "kepala_bagian", "unit" => "Pensosbud", "allowed_menus" => ["PENSOSBUD"], "password_hash" => $hash],
+ "ekonomi.head" => ["name" => "Kabid Ekonomi", "role" => "kepala_bagian", "unit" => "Ekonomi & Perdagangan", "allowed_menus" => ["EKONOMI & PERDAGANGAN"], "password_hash" => $hash],
+ "konsuler.head" => ["name" => "Kabid Kekonsuleran", "role" => "kepala_bagian", "unit" => "Kekonsuleran", "allowed_menus" => ["KEKONSULERAN"], "password_hash" => $hash],
+ "hoc.head" => ["name" => "Kabid Kanselerai/HOC", "role" => "kepala_bagian", "unit" => "Kanselerai / HOC", "allowed_menus" => ["KANSELERAI / HOC"], "password_hash" => $hash],
+ "pid.head" => ["name" => "Kabid PID", "role" => "kepala_bagian", "unit" => "PID", "allowed_menus" => ["PID"], "password_hash" => $hash],
+ "admin.head" => ["name" => "Kabid Admin & Internal", "role" => "kepala_bagian", "unit" => "Admin & Internal", "allowed_menus" => ["ADMIN & INTERNAL", "Gallery"], "password_hash" => $hash],
+ "politik.staff" => ["name" => "Staf Politik", "role" => "staf", "unit" => "Politik", "allowed_menus" => ["INFORMASI NEGARA", "POLITIK"], "password_hash" => $hash],
+ "pensosbud.staff" => ["name" => "Staf Pensosbud", "role" => "staf", "unit" => "Pensosbud", "allowed_menus" => ["PENSOSBUD"], "password_hash" => $hash],
+ "ekonomi.staff" => ["name" => "Staf Ekonomi", "role" => "staf", "unit" => "Ekonomi & Perdagangan", "allowed_menus" => ["EKONOMI & PERDAGANGAN"], "password_hash" => $hash],
+ "konsuler.staff" => ["name" => "Staf Konsuler", "role" => "staf", "unit" => "Kekonsuleran", "allowed_menus" => ["KEKONSULERAN"], "password_hash" => $hash],
+ "admin.staff" => ["name" => "Staf Admin", "role" => "staf", "unit" => "Admin & Internal", "allowed_menus" => ["ADMIN & INTERNAL", "Gallery"], "password_hash" => $hash],
+ ];
+
+ return $users;
+}
+
+function role_badge_label(string $role): string
+{
+ return match ($role) {
+ "super_admin" => "Super Admin",
+ "kepala_bagian" => "Kepala Bagian",
+ default => "Staf",
+ };
+}
+
+function current_user(): ?array
+{
+ $username = $_SESSION["auth_username"] ?? null;
+ if (!$username) {
+ return null;
+ }
+
+ $catalog = users_catalog();
+ if (!isset($catalog[$username])) {
+ unset($_SESSION["auth_username"]);
+ return null;
+ }
+
+ return ["username" => $username] + $catalog[$username];
+}
+
+function require_login(): array
+{
+ $user = current_user();
+ if (!$user) {
+ flash("error", "Silakan masuk dengan username dan password internal Anda.");
+ header("Location: index.php");
+ exit;
+ }
+
+ return $user;
+}
+
+function csrf_token(): string
+{
+ if (empty($_SESSION["csrf_token"])) {
+ $_SESSION["csrf_token"] = bin2hex(random_bytes(24));
+ }
+ return $_SESSION["csrf_token"];
+}
+
+function verify_csrf_or_fail(): void
+{
+ $token = $_POST["csrf_token"] ?? "";
+ if (!hash_equals($_SESSION["csrf_token"] ?? "", $token)) {
+ http_response_code(419);
+ exit("CSRF token tidak valid.");
+ }
+}
+
+function flash(string $type, string $message): void
+{
+ $_SESSION["flash"] = ["type" => $type, "message" => $message];
+}
+
+function pull_flash(): ?array
+{
+ if (empty($_SESSION["flash"])) {
+ return null;
+ }
+
+ $flash = $_SESSION["flash"];
+ unset($_SESSION["flash"]);
+ return $flash;
+}
+
+function ensure_archive_table(): void
+{
+ db()->exec(
+ "CREATE TABLE IF NOT EXISTS archive_records (
+ id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ reference_code VARCHAR(100) NOT NULL,
+ title VARCHAR(180) NOT NULL,
+ main_menu VARCHAR(120) NOT NULL,
+ folder_path VARCHAR(255) NOT NULL,
+ country_tag VARCHAR(40) NOT NULL,
+ owner_unit VARCHAR(120) NOT NULL,
+ created_by_username VARCHAR(80) NOT NULL,
+ created_by_name VARCHAR(120) NOT NULL,
+ record_day TINYINT UNSIGNED NOT NULL,
+ record_month TINYINT UNSIGNED NOT NULL,
+ record_year SMALLINT UNSIGNED NOT NULL,
+ document_date DATE DEFAULT NULL,
+ confidentiality VARCHAR(40) NOT NULL,
+ keywords VARCHAR(255) DEFAULT NULL,
+ description TEXT NOT NULL,
+ attachment_name VARCHAR(255) DEFAULT NULL,
+ attachment_path VARCHAR(255) DEFAULT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ INDEX idx_folder_path (folder_path),
+ INDEX idx_main_menu (main_menu),
+ INDEX idx_creator (created_by_username),
+ INDEX idx_owner_unit (owner_unit)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
+ );
+}
+
+function upload_dir(): string
+{
+ $path = __DIR__ . "/uploads/archives";
+ if (!is_dir($path)) {
+ mkdir($path, 0775, true);
+ }
+ return $path;
+}
+
+function allowed_file_extensions(): array
+{
+ return ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "jpg", "jpeg", "png", "mp4", "zip"];
+}
+
+function can_access_menu(array $user, string $mainMenu): bool
+{
+ return $user["role"] === "super_admin" || in_array($mainMenu, $user["allowed_menus"], true);
+}
+
+function can_view_record(array $user, array $record): bool
+{
+ if ($user["role"] === "super_admin" || $user["role"] === "kepala_bagian") {
+ return true;
+ }
+ return $record["created_by_username"] === $user["username"];
+}
+
+function can_edit_record(array $user, array $record): bool
+{
+ if ($user["role"] === "super_admin") {
+ return true;
+ }
+ if ($user["role"] === "kepala_bagian") {
+ return $record["owner_unit"] === $user["unit"];
+ }
+ return $record["created_by_username"] === $user["username"];
+}
+
+function can_delete_record(array $user, array $record): bool
+{
+ if ($user["role"] === "super_admin") {
+ return true;
+ }
+ return $user["role"] === "staf" && $record["created_by_username"] === $user["username"];
+}
+
+function available_folder_paths_for_user(array $user): array
+{
+ $paths = [];
+ foreach (flatten_tree(archive_tree()) as $node) {
+ if ($node["has_children"]) {
+ continue;
+ }
+ if (can_access_menu($user, $node["main_menu"])) {
+ $paths[] = $node["path"];
+ }
+ }
+ return $paths;
+}
+
+function normalize_folder_path(string $folderPath): ?array
+{
+ $lookup = folder_lookup();
+ return $lookup[$folderPath] ?? null;
+}
+
+function validate_record_date(int $day, int $month, int $year): ?string
+{
+ if ($year < 2000 || $year > 2100) {
+ return null;
+ }
+ if (!checkdate($month, $day, $year)) {
+ return null;
+ }
+ return sprintf("%04d-%02d-%02d", $year, $month, $day);
+}
+
+function fetch_record_by_id(int $id): ?array
+{
+ $stmt = db()->prepare("SELECT * FROM archive_records WHERE id = :id LIMIT 1");
+ $stmt->bindValue(":id", $id, PDO::PARAM_INT);
+ $stmt->execute();
+ return $stmt->fetch() ?: null;
+}
+
+function fetch_records(array $user, ?string $folderFilter = null, int $limit = 12): array
+{
+ $sql = "SELECT * FROM archive_records";
+ $conditions = [];
+ $params = [];
+
+ if ($user["role"] === "staf") {
+ $conditions[] = "created_by_username = :username";
+ $params[":username"] = $user["username"];
+ }
+
+ if ($folderFilter) {
+ $conditions[] = "folder_path = :folder_path";
+ $params[":folder_path"] = $folderFilter;
+ }
+
+ if ($conditions) {
+ $sql .= " WHERE " . implode(" AND ", $conditions);
+ }
+
+ $sql .= " ORDER BY updated_at DESC LIMIT :limit_rows";
+ $stmt = db()->prepare($sql);
+ foreach ($params as $key => $value) {
+ $stmt->bindValue($key, $value, PDO::PARAM_STR);
+ }
+ $stmt->bindValue(":limit_rows", $limit, PDO::PARAM_INT);
+ $stmt->execute();
+ return $stmt->fetchAll();
+}
+
+function fetch_summary_counts(array $user): array
+{
+ $sql = "SELECT COUNT(*) AS total_records,
+ SUM(CASE WHEN attachment_path IS NOT NULL AND attachment_path <> '' THEN 1 ELSE 0 END) AS total_files,
+ SUM(CASE WHEN DATE(created_at) = CURDATE() THEN 1 ELSE 0 END) AS created_today
+ FROM archive_records";
+ $params = [];
+
+ if ($user["role"] === "staf") {
+ $sql .= " WHERE created_by_username = :username";
+ $params[":username"] = $user["username"];
+ }
+
+ $stmt = db()->prepare($sql);
+ foreach ($params as $key => $value) {
+ $stmt->bindValue($key, $value, PDO::PARAM_STR);
+ }
+ $stmt->execute();
+
+ return $stmt->fetch() ?: ["total_records" => 0, "total_files" => 0, "created_today" => 0];
+}
+
+function render_tree_nodes(array $nodes, ?string $activeFolder, array $user, array $parents = []): string
+{
+ $html = '
';
+ return $html;
+}
+
+function h(?string $value): string
+{
+ return htmlspecialchars((string) $value, ENT_QUOTES, "UTF-8");
+}
+
+function alert_class(string $type): string
+{
+ return match ($type) {
+ "success" => "success",
+ "warning" => "warning",
+ default => "danger",
+ };
+}
+
+function record_badge_class(string $confidentiality): string
+{
+ return match ($confidentiality) {
+ "Rahasia" => "text-bg-dark",
+ "Terbatas" => "text-bg-secondary",
+ default => "text-bg-light border",
+ };
+}
diff --git a/archive_delete.php b/archive_delete.php
new file mode 100644
index 0000000..fd66a71
--- /dev/null
+++ b/archive_delete.php
@@ -0,0 +1,26 @@
+prepare('DELETE FROM archive_records WHERE id = :id LIMIT 1');
+$stmt->bindValue(':id', $id, PDO::PARAM_INT);
+$stmt->execute();
+
+if (!empty($record['attachment_path']) && is_file(__DIR__ . '/' . $record['attachment_path'])) {
+ @unlink(__DIR__ . '/' . $record['attachment_path']);
+}
+
+flash('success', 'Arsip berhasil dihapus.');
+header('Location: index.php');
diff --git a/archive_detail.php b/archive_detail.php
new file mode 100644
index 0000000..b3eae38
--- /dev/null
+++ b/archive_detail.php
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+ = h($record['title']) ?> — = h($projectName) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Detail arsip
+
= h($record['title']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
Nomor referensi
+
= h($record['reference_code']) ?>
+
= h($record['folder_path']) ?>
+
+
= h($record['confidentiality']) ?>
+
+
+
+
+
Tanggal arsip
+
= h($record['record_day']) ?>/= h($record['record_month']) ?>/= h($record['record_year']) ?>
+
= h((string) $record['document_date']) ?>
+
+
+
+
+
Pemilik unit
+
= h($record['owner_unit']) ?>
+
Dibuat oleh = h($record['created_by_name']) ?>
+
+
+
+
+
Negara / cakupan
+
= h($record['country_tag']) ?>
+
Menu = h($record['main_menu']) ?>
+
+
+
+
+ Ringkasan dokumen
+ = nl2br(h($record['description'])) ?>
+
+
+
+
+
+
+
Metadata
+
Informasi dokumen
+
+
Kata kunci = h($record['keywords'] ?: '—') ?>
+
Penginput = h($record['created_by_name']) ?> · = h($record['created_by_username']) ?>
+
Dibuat = h((string) $record['created_at']) ?>
+
Diperbarui = h((string) $record['updated_at']) ?>
+
+
+
+
Lampiran
+
Aksi cepat
+
+
+
= h($record['attachment_name']) ?>
+
Lampiran arsip siap diunduh sesuai hak akses.
+
+
+
+
+
Belum ada lampiran.
+
Arsip ini masih tersimpan sebagai entri metadata dan ringkasan dokumen.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/archive_download.php b/archive_download.php
new file mode 100644
index 0000000..f6a4f8b
--- /dev/null
+++ b/archive_download.php
@@ -0,0 +1,26 @@
+ 0) {
+ $existing = fetch_record_by_id($id);
+ if (!$existing || !can_edit_record($user, $existing)) {
+ flash('error', 'Arsip tidak ditemukan atau tidak dapat diedit.');
+ header('Location: index.php');
+ exit;
+ }
+}
+
+$attachmentName = $existing['attachment_name'] ?? null;
+$attachmentPath = $existing['attachment_path'] ?? null;
+if (!empty($_FILES['attachment']['name'])) {
+ if (!isset($_FILES['attachment']['error']) || $_FILES['attachment']['error'] !== UPLOAD_ERR_OK) {
+ flash('error', 'Lampiran gagal diunggah.');
+ header('Location: index.php#arsip-form');
+ exit;
+ }
+ if ((int) $_FILES['attachment']['size'] > 8 * 1024 * 1024) {
+ flash('error', 'Ukuran file maksimal 8 MB.');
+ header('Location: index.php#arsip-form');
+ exit;
+ }
+ $originalName = basename((string) $_FILES['attachment']['name']);
+ $extension = strtolower((string) pathinfo($originalName, PATHINFO_EXTENSION));
+ if (!in_array($extension, allowed_file_extensions(), true)) {
+ flash('error', 'Format lampiran belum didukung.');
+ header('Location: index.php#arsip-form');
+ exit;
+ }
+ $safeName = date('YmdHis') . '-' . bin2hex(random_bytes(6)) . '.' . $extension;
+ $relativePath = 'uploads/archives/' . $safeName;
+ $destination = __DIR__ . '/' . $relativePath;
+ upload_dir();
+ if (!move_uploaded_file($_FILES['attachment']['tmp_name'], $destination)) {
+ flash('error', 'Lampiran gagal disimpan ke server.');
+ header('Location: index.php#arsip-form');
+ exit;
+ }
+ if ($attachmentPath && is_file(__DIR__ . '/' . $attachmentPath)) {
+ @unlink(__DIR__ . '/' . $attachmentPath);
+ }
+ $attachmentName = $originalName;
+ $attachmentPath = $relativePath;
+}
+
+$ownerUnit = $user['unit'];
+if ($mainMenu === 'INFORMASI NEGARA' && in_array($user['unit'], ['Politik', 'Pimpinan'], true)) {
+ $ownerUnit = 'Politik';
+}
+
+if ($id > 0) {
+ $stmt = db()->prepare('UPDATE archive_records SET
+ reference_code = :reference_code,
+ title = :title,
+ main_menu = :main_menu,
+ folder_path = :folder_path,
+ country_tag = :country_tag,
+ owner_unit = :owner_unit,
+ record_day = :record_day,
+ record_month = :record_month,
+ record_year = :record_year,
+ document_date = :document_date,
+ confidentiality = :confidentiality,
+ keywords = :keywords,
+ description = :description,
+ attachment_name = :attachment_name,
+ attachment_path = :attachment_path
+ WHERE id = :id');
+ $stmt->bindValue(':id', $id, PDO::PARAM_INT);
+} else {
+ $stmt = db()->prepare('INSERT INTO archive_records (
+ reference_code, title, main_menu, folder_path, country_tag, owner_unit,
+ created_by_username, created_by_name, record_day, record_month, record_year,
+ document_date, confidentiality, keywords, description, attachment_name, attachment_path
+ ) VALUES (
+ :reference_code, :title, :main_menu, :folder_path, :country_tag, :owner_unit,
+ :created_by_username, :created_by_name, :record_day, :record_month, :record_year,
+ :document_date, :confidentiality, :keywords, :description, :attachment_name, :attachment_path
+ )');
+ $stmt->bindValue(':created_by_username', $user['username'], PDO::PARAM_STR);
+ $stmt->bindValue(':created_by_name', $user['name'], PDO::PARAM_STR);
+}
+
+$stmt->bindValue(':reference_code', $referenceCode, PDO::PARAM_STR);
+$stmt->bindValue(':title', $title, PDO::PARAM_STR);
+$stmt->bindValue(':main_menu', $mainMenu, PDO::PARAM_STR);
+$stmt->bindValue(':folder_path', $folderPath, PDO::PARAM_STR);
+$stmt->bindValue(':country_tag', $countryTag, PDO::PARAM_STR);
+$stmt->bindValue(':owner_unit', $ownerUnit, PDO::PARAM_STR);
+$stmt->bindValue(':record_day', $recordDay, PDO::PARAM_INT);
+$stmt->bindValue(':record_month', $recordMonth, PDO::PARAM_INT);
+$stmt->bindValue(':record_year', $recordYear, PDO::PARAM_INT);
+$stmt->bindValue(':document_date', $documentDate, PDO::PARAM_STR);
+$stmt->bindValue(':confidentiality', $confidentiality, PDO::PARAM_STR);
+$stmt->bindValue(':keywords', $keywords !== '' ? $keywords : null, $keywords !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
+$stmt->bindValue(':description', $description, PDO::PARAM_STR);
+$stmt->bindValue(':attachment_name', $attachmentName, $attachmentName ? PDO::PARAM_STR : PDO::PARAM_NULL);
+$stmt->bindValue(':attachment_path', $attachmentPath, $attachmentPath ? PDO::PARAM_STR : PDO::PARAM_NULL);
+$stmt->execute();
+
+$recordId = $id > 0 ? $id : (int) db()->lastInsertId();
+flash('success', $id > 0 ? 'Perubahan arsip berhasil disimpan.' : 'Arsip baru berhasil ditambahkan ke database.');
+header('Location: archive_detail.php?id=' . $recordId);
diff --git a/assets/css/custom.css b/assets/css/custom.css
index 789132e..ecbcece 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,403 +1,336 @@
-body {
- background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
- background-size: 400% 400%;
- animation: gradient 15s ease infinite;
- color: #212529;
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
- font-size: 14px;
- margin: 0;
- min-height: 100vh;
+/* KBRI Harare Archive Desk */
+:root {
+ --bg: #f4f5f7;
+ --surface: #ffffff;
+ --surface-alt: #f8f9fb;
+ --border: #d7dce3;
+ --border-strong: #c3cad4;
+ --text: #101828;
+ --text-muted: #667085;
+ --primary: #1f2937;
+ --primary-soft: #eef1f4;
+ --accent: #4b5563;
+ --success: #0f766e;
+ --warning: #92400e;
+ --danger: #b42318;
+ --shadow-sm: 0 10px 30px rgba(16, 24, 40, 0.04);
+ --shadow-lg: 0 22px 50px rgba(16, 24, 40, 0.08);
+ --radius-sm: 10px;
+ --radius-md: 14px;
+ --radius-lg: 18px;
+ --space-1: 0.25rem;
+ --space-2: 0.5rem;
+ --space-3: 0.75rem;
+ --space-4: 1rem;
+ --space-5: 1.5rem;
+ --space-6: 2rem;
}
-.main-wrapper {
+html { scroll-behavior: smooth; }
+body {
+ margin: 0;
+ background: var(--bg);
+ color: var(--text);
+ font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ line-height: 1.5;
+}
+
+a { color: inherit; text-decoration: none; }
+a:hover { color: inherit; }
+
+.auth-shell,
+.app-shell {
+ min-height: 100vh;
+ background: #f4f5f7;
+}
+
+.topbar {
+ background: rgba(255,255,255,0.92);
+ backdrop-filter: blur(10px);
+}
+
+.panel,
+.metric-card,
+.record-card,
+.file-card,
+.empty-state {
+ background: rgba(255,255,255,0.98);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-sm);
+}
+
+.panel { padding: var(--space-5); }
+.panel-hero {
+ position: relative;
+ overflow: hidden;
+ box-shadow: var(--shadow-lg);
+}
+.panel-hero::after {
+ content: "";
+ position: absolute;
+ inset: auto -10% -40% auto;
+ width: 220px;
+ height: 220px;
+ border-radius: 50%;
+ background: rgba(31, 41, 55, 0.05);
+ box-shadow: inset -24px -24px 40px rgba(255,255,255,0.7), 20px 20px 40px rgba(31,41,55,0.05);
+}
+
+.eyebrow {
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ font-size: 0.72rem;
+ font-weight: 700;
+ color: var(--text-muted);
+}
+
+.display-title,
+.app-title,
+.section-title {
+ letter-spacing: -0.02em;
+ font-weight: 700;
+ color: var(--text);
+}
+.display-title { font-size: clamp(2rem, 4vw, 3.35rem); max-width: 12ch; }
+.app-title { font-size: 1.35rem; }
+.section-title { font-size: 1.15rem; }
+.lead-copy,
+.text-secondary,
+.form-text,
+.inline-note,
+.record-meta,
+.record-path,
+.policy-list li,
+.empty-state p,
+.callout,
+small { color: var(--text-muted) !important; }
+
+.badge-soft {
+ background: var(--primary-soft);
+ color: var(--primary);
+ border: 1px solid var(--border);
+ font-weight: 600;
+}
+.badge-outline {
+ background: transparent;
+ color: var(--text);
+ border: 1px solid var(--border-strong);
+ font-weight: 600;
+}
+
+.metric-card {
+ padding: 1rem 1.1rem;
+ position: relative;
+}
+.metric-card::before {
+ content: "";
+ position: absolute;
+ inset: 12px 12px auto auto;
+ width: 52px;
+ height: 52px;
+ border-radius: 14px;
+ border: 1px solid rgba(31,41,55,0.08);
+ background: rgba(255,255,255,0.72);
+ box-shadow: 12px 12px 25px rgba(31,41,55,0.04), inset -10px -10px 15px rgba(255,255,255,0.9);
+}
+.metric-label {
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--text-muted);
+ margin-bottom: 0.4rem;
+ position: relative;
+ z-index: 1;
+}
+.metric-value {
+ font-size: 1.9rem;
+ font-weight: 700;
+ letter-spacing: -0.03em;
+ position: relative;
+ z-index: 1;
+}
+.small-value { font-size: 1.3rem; }
+
+.callout,
+.inline-note,
+.file-card,
+.empty-state {
+ padding: 0.9rem 1rem;
+ border-radius: var(--radius-md);
+ background: var(--surface-alt);
+ border: 1px solid var(--border);
+}
+
+.form-control,
+.form-select {
+ border-radius: var(--radius-sm);
+ border-color: var(--border-strong);
+ padding: 0.8rem 0.9rem;
+ color: var(--text);
+ background-color: #fff;
+}
+.form-control:focus,
+.form-select:focus {
+ border-color: #98a2b3;
+ box-shadow: 0 0 0 0.2rem rgba(17,24,39,0.08);
+}
+.form-label {
+ font-size: 0.88rem;
+ font-weight: 600;
+ color: var(--text);
+}
+
+.btn {
+ border-radius: 12px;
+ font-weight: 600;
+ padding: 0.72rem 1rem;
+}
+.btn-dark {
+ background: #111827;
+ border-color: #111827;
+ box-shadow: 0 10px 25px rgba(17, 24, 39, 0.18);
+}
+.btn-dark:hover,
+.btn-dark:focus { background: #0b1220; border-color: #0b1220; }
+.btn-outline-secondary,
+.btn-outline-danger {
+ border-color: var(--border-strong);
+}
+
+.tree-nav {
+ max-height: 70vh;
+ overflow: auto;
+ padding-right: 0.35rem;
+}
+.tree-level { padding-left: 0; }
+.tree-item + .tree-item { margin-top: 0.35rem; }
+.tree-toggle,
+.tree-leaf {
+ width: 100%;
display: flex;
align-items: center;
+ gap: 0.7rem;
+ border: 0;
+ background: transparent;
+ padding: 0.65rem 0.7rem;
+ border-radius: 12px;
+ text-align: left;
+}
+.tree-toggle:hover,
+.tree-leaf:hover,
+.tree-leaf.active {
+ background: var(--primary-soft);
+}
+.tree-icon {
+ width: 22px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
justify-content: center;
- min-height: 100vh;
- width: 100%;
- padding: 20px;
- box-sizing: border-box;
- position: relative;
- z-index: 1;
-}
-
-@keyframes gradient {
- 0% {
- background-position: 0% 50%;
- }
- 50% {
- background-position: 100% 50%;
- }
- 100% {
- background-position: 0% 50%;
- }
-}
-
-.chat-container {
- width: 100%;
- max-width: 600px;
- background: rgba(255, 255, 255, 0.85);
- border: 1px solid rgba(255, 255, 255, 0.3);
- border-radius: 20px;
- display: flex;
- flex-direction: column;
- height: 85vh;
- box-shadow: 0 20px 40px rgba(0,0,0,0.2);
- backdrop-filter: blur(15px);
- -webkit-backdrop-filter: blur(15px);
- overflow: hidden;
-}
-
-.chat-header {
- padding: 1.5rem;
- border-bottom: 1px solid rgba(0, 0, 0, 0.05);
- background: rgba(255, 255, 255, 0.5);
+ border-radius: 8px;
+ border: 1px solid var(--border-strong);
+ background: #fff;
font-weight: 700;
- font-size: 1.1rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
+ flex: 0 0 22px;
}
-
-.chat-messages {
- flex: 1;
- overflow-y: auto;
- padding: 1.5rem;
- display: flex;
- flex-direction: column;
- gap: 1.25rem;
-}
-
-/* Custom Scrollbar */
-::-webkit-scrollbar {
- width: 6px;
-}
-
-::-webkit-scrollbar-track {
- background: transparent;
-}
-
-::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.3);
- border-radius: 10px;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.5);
-}
-
-.message {
- max-width: 85%;
- padding: 0.85rem 1.1rem;
- border-radius: 16px;
- line-height: 1.5;
- font-size: 0.95rem;
- box-shadow: 0 4px 15px rgba(0,0,0,0.05);
- animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
-}
-
-@keyframes fadeIn {
- from { opacity: 0; transform: translateY(20px) scale(0.95); }
- to { opacity: 1; transform: translateY(0) scale(1); }
-}
-
-.message.visitor {
- align-self: flex-end;
- background: linear-gradient(135deg, #212529 0%, #343a40 100%);
+.tree-toggle.open .tree-icon { content: "−"; }
+.tree-toggle.open .tree-icon,
+.tree-leaf.active .tree-dot {
+ background: #111827;
color: #fff;
- border-bottom-right-radius: 4px;
+ border-color: #111827;
+}
+.tree-label { font-size: 0.94rem; color: var(--text); }
+.tree-children {
+ display: none;
+ margin-left: 0.9rem;
+ border-left: 1px dashed var(--border);
+ padding-left: 0.75rem;
+}
+.tree-children.show { display: block; }
+.tree-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 999px;
+ border: 1px solid var(--border-strong);
+ background: #fff;
+ flex: 0 0 10px;
}
-.message.bot {
- align-self: flex-start;
- background: #ffffff;
- color: #212529;
- border-bottom-left-radius: 4px;
+.record-card {
+ padding: 1rem;
+ transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
-
-.chat-input-area {
- padding: 1.25rem;
- background: rgba(255, 255, 255, 0.5);
- border-top: 1px solid rgba(0, 0, 0, 0.05);
-}
-
-.chat-input-area form {
- display: flex;
- gap: 0.75rem;
-}
-
-.chat-input-area input {
- flex: 1;
- border: 1px solid rgba(0, 0, 0, 0.1);
- border-radius: 12px;
- padding: 0.75rem 1rem;
- outline: none;
- background: rgba(255, 255, 255, 0.9);
- transition: all 0.3s ease;
-}
-
-.chat-input-area input:focus {
- border-color: #23a6d5;
- box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
-}
-
-.chat-input-area button {
- background: #212529;
- color: #fff;
- border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 12px;
- cursor: pointer;
- font-weight: 600;
- transition: all 0.3s ease;
-}
-
-.chat-input-area button:hover {
- background: #000;
+.record-card + .record-card { margin-top: 0.85rem; }
+.record-card:hover,
+.record-card.active {
transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(0,0,0,0.2);
+ box-shadow: var(--shadow-lg);
+ border-color: #b6bec9;
}
+.record-title { font-size: 1rem; margin-bottom: 0.35rem; }
+.record-meta,
+.record-path { font-size: 0.84rem; }
+.record-path { min-height: 2.4rem; }
-/* Background Animations */
-.bg-animations {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 0;
- overflow: hidden;
- pointer-events: none;
+.credential-table th,
+.credential-table td,
+.meta-grid dt,
+.meta-grid dd { font-size: 0.9rem; }
+.meta-grid div + div { border-top: 1px solid var(--border); }
+.meta-grid dt {
+ color: var(--text-muted);
+ margin-bottom: 0.25rem;
+ padding-top: 0.9rem;
}
-
-.blob {
- position: absolute;
- width: 500px;
- height: 500px;
- background: rgba(255, 255, 255, 0.2);
- border-radius: 50%;
- filter: blur(80px);
- animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
-}
-
-.blob-1 {
- top: -10%;
- left: -10%;
- background: rgba(238, 119, 82, 0.4);
-}
-
-.blob-2 {
- bottom: -10%;
- right: -10%;
- background: rgba(35, 166, 213, 0.4);
- animation-delay: -7s;
- width: 600px;
- height: 600px;
-}
-
-.blob-3 {
- top: 40%;
- left: 30%;
- background: rgba(231, 60, 126, 0.3);
- animation-delay: -14s;
- width: 450px;
- height: 450px;
-}
-
-@keyframes move {
- 0% { transform: translate(0, 0) rotate(0deg) scale(1); }
- 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
- 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
- 100% { transform: translate(0, 0) rotate(360deg) scale(1); }
-}
-
-.header-link {
- font-size: 14px;
- color: #fff;
- text-decoration: none;
- background: rgba(0, 0, 0, 0.2);
- padding: 0.5rem 1rem;
- border-radius: 8px;
- transition: all 0.3s ease;
-}
-
-.header-link:hover {
- background: rgba(0, 0, 0, 0.4);
- text-decoration: none;
-}
-
-/* Admin Styles */
-.admin-container {
- max-width: 900px;
- margin: 3rem auto;
- padding: 2.5rem;
- background: rgba(255, 255, 255, 0.85);
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- border-radius: 24px;
- box-shadow: 0 20px 50px rgba(0,0,0,0.15);
- border: 1px solid rgba(255, 255, 255, 0.4);
- position: relative;
- z-index: 1;
-}
-
-.admin-container h1 {
- margin-top: 0;
- color: #212529;
- font-weight: 800;
-}
-
-.table {
- width: 100%;
- border-collapse: separate;
- border-spacing: 0 8px;
- margin-top: 1.5rem;
-}
-
-.table th {
- background: transparent;
- border: none;
- padding: 1rem;
- color: #6c757d;
+.meta-grid dd {
+ margin-bottom: 0;
font-weight: 600;
+ padding-bottom: 0.9rem;
+}
+
+.policy-list {
+ padding-left: 1rem;
+ display: grid;
+ gap: 0.55rem;
+}
+.small-heading {
+ font-size: 0.82rem;
+ letter-spacing: 0.08em;
text-transform: uppercase;
- font-size: 0.75rem;
- letter-spacing: 1px;
-}
-
-.table td {
- background: #fff;
- padding: 1rem;
- border: none;
-}
-
-.table tr td:first-child { border-radius: 12px 0 0 12px; }
-.table tr td:last-child { border-radius: 0 12px 12px 0; }
-
-.form-group {
- margin-bottom: 1.25rem;
-}
-
-.form-group label {
- display: block;
- margin-bottom: 0.5rem;
- font-weight: 600;
- font-size: 0.9rem;
-}
-
-.form-control {
- width: 100%;
- padding: 0.75rem 1rem;
- border: 1px solid rgba(0, 0, 0, 0.1);
- border-radius: 12px;
- background: #fff;
- transition: all 0.3s ease;
- box-sizing: border-box;
-}
-
-.form-control:focus {
- outline: none;
- border-color: #23a6d5;
- box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
-}
-
-.header-container {
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.header-links {
- display: flex;
- gap: 1rem;
-}
-
-.admin-card {
- background: rgba(255, 255, 255, 0.6);
- padding: 2rem;
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.5);
- margin-bottom: 2.5rem;
- box-shadow: 0 10px 30px rgba(0,0,0,0.05);
-}
-
-.admin-card h3 {
- margin-top: 0;
- margin-bottom: 1.5rem;
font-weight: 700;
+ color: var(--text-muted);
+}
+.detail-body {
+ border-top: 1px solid var(--border);
+ padding-top: 1rem;
}
-.btn-delete {
- background: #dc3545;
- color: white;
- border: none;
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- cursor: pointer;
+.toast {
+ border-radius: 14px;
+ box-shadow: var(--shadow-sm);
}
-.btn-add {
- background: #212529;
- color: white;
- border: none;
- padding: 0.5rem 1rem;
- border-radius: 4px;
- cursor: pointer;
- margin-top: 1rem;
+.no-print { display: block; }
+
+@media (max-width: 991.98px) {
+ .panel { padding: 1rem; }
+ .tree-nav { max-height: none; }
+ .display-title { max-width: none; }
}
-.btn-save {
- background: #0088cc;
- color: white;
- border: none;
- padding: 0.8rem 1.5rem;
- border-radius: 12px;
- cursor: pointer;
- font-weight: 600;
- width: 100%;
- transition: all 0.3s ease;
+@media print {
+ body { background: #fff; }
+ .no-print,
+ .btn,
+ .topbar { display: none !important; }
+ .panel,
+ .metric-card,
+ .file-card {
+ box-shadow: none;
+ border-color: #d1d5db;
+ background: #fff;
+ }
+ .print-panel { padding: 0; border: 0; }
}
-
-.webhook-url {
- font-size: 0.85em;
- color: #555;
- margin-top: 0.5rem;
-}
-
-.history-table-container {
- overflow-x: auto;
- background: rgba(255, 255, 255, 0.4);
- padding: 1rem;
- border-radius: 12px;
- border: 1px solid rgba(255, 255, 255, 0.3);
-}
-
-.history-table {
- width: 100%;
-}
-
-.history-table-time {
- width: 15%;
- white-space: nowrap;
- font-size: 0.85em;
- color: #555;
-}
-
-.history-table-user {
- width: 35%;
- background: rgba(255, 255, 255, 0.3);
- border-radius: 8px;
- padding: 8px;
-}
-
-.history-table-ai {
- width: 50%;
- background: rgba(255, 255, 255, 0.5);
- border-radius: 8px;
- padding: 8px;
-}
-
-.no-messages {
- text-align: center;
- color: #777;
-}
\ No newline at end of file
diff --git a/assets/js/main.js b/assets/js/main.js
index d349598..0fde0a0 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1,39 +1,46 @@
document.addEventListener('DOMContentLoaded', () => {
- const chatForm = document.getElementById('chat-form');
- const chatInput = document.getElementById('chat-input');
- const chatMessages = document.getElementById('chat-messages');
+ document.querySelectorAll('.tree-toggle').forEach((toggle) => {
+ const targetSelector = toggle.getAttribute('data-tree-target');
+ const target = targetSelector ? document.querySelector(targetSelector) : null;
+ const icon = toggle.querySelector('.tree-icon');
+ const syncState = () => {
+ const open = target && target.classList.contains('show');
+ toggle.classList.toggle('open', !!open);
+ toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
+ if (icon) {
+ icon.textContent = open ? '−' : '+';
+ }
+ };
+ syncState();
+ toggle.addEventListener('click', () => {
+ if (target) {
+ target.classList.toggle('show');
+ }
+ syncState();
+ });
+ });
- const appendMessage = (text, sender) => {
- const msgDiv = document.createElement('div');
- msgDiv.classList.add('message', sender);
- msgDiv.textContent = text;
- chatMessages.appendChild(msgDiv);
- chatMessages.scrollTop = chatMessages.scrollHeight;
- };
+ document.querySelectorAll('.toast').forEach((el) => {
+ const toast = new bootstrap.Toast(el, { delay: 4500 });
+ toast.show();
+ });
- chatForm.addEventListener('submit', async (e) => {
- e.preventDefault();
- const message = chatInput.value.trim();
- if (!message) return;
+ const folderSelect = document.getElementById('folder_path');
+ const mainMenuSelect = document.getElementById('main_menu');
+ if (folderSelect && mainMenuSelect) {
+ const syncMenuFromFolder = () => {
+ const selectedOption = folderSelect.options[folderSelect.selectedIndex];
+ if (!selectedOption || !selectedOption.value) return;
+ const mainMenu = selectedOption.value.split(' / ')[0];
+ if ([...mainMenuSelect.options].some((option) => option.value === mainMenu)) {
+ mainMenuSelect.value = mainMenu;
+ }
+ };
+ folderSelect.addEventListener('change', syncMenuFromFolder);
+ syncMenuFromFolder();
+ }
- appendMessage(message, 'visitor');
- chatInput.value = '';
-
- try {
- const response = await fetch('api/chat.php', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ message })
- });
- const data = await response.json();
-
- // Artificial delay for realism
- setTimeout(() => {
- appendMessage(data.reply, 'bot');
- }, 500);
- } catch (error) {
- console.error('Error:', error);
- appendMessage("Sorry, something went wrong. Please try again.", 'bot');
- }
+ document.querySelectorAll('[data-print-trigger]').forEach((button) => {
+ button.addEventListener('click', () => window.print());
});
});
diff --git a/auth_login.php b/auth_login.php
new file mode 100644
index 0000000..28920f7
--- /dev/null
+++ b/auth_login.php
@@ -0,0 +1,21 @@
+ 'Januari', 2 => 'Februari', 3 => 'Maret', 4 => 'April', 5 => 'Mei', 6 => 'Juni',
+ 7 => 'Juli', 8 => 'Agustus', 9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Desember',
+];
+$yearOptions = range((int) date('Y') + 1, 2020);
+$loginUsers = users_catalog();
?>
-
+
-
-
- New Style
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ = h($projectName) ?> — Arsip Internal
+
+
+
+
+
+
+
+
+
+
+
-
-
-
Analyzing your requirements and generating your website…
-
- Loading…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
+
+
+
+
+
+
+
+
+
Portal Arsip Internal KBRI Harare
+
Arsip terstruktur, aman, dan siap dipakai untuk alur kerja harian.
+
Versi awal ini sudah menyiapkan login internal, struktur menu tree lengkap, input dokumen bertanggal, daftar arsip, detail, edit, print, dan unduh lampiran.
+
+
+
+
Peran awal
+
14 akun
+
2 super admin, 7 kepala bagian, 5 staf
+
+
+
+
+
Kontrol kerja
+
Tree + RBAC
+
Akses folder sesuai unit kerja
+
+
+
+
+ Default keamanan tahap awal: semua akun demo memakai password Kbri2026!. Setelah alur disetujui, langkah paling penting berikutnya adalah password individual + audit trail lanjutan.
+
+
+
+
+
+
+
+
Masuk
+
Akun internal
+
Gunakan salah satu username resmi di bawah.
+
+
High security MVP
+
+
+
= h($flash['message']) ?>
+
+
+
+
+
Akun demo siap pakai
+ password sama untuk semua
+
+
+
+
+
+ Username
+ Role
+
+
+
+ $account): ?>
+
+ = h($username) ?>
+ = h(role_badge_label($account['role'])) ?> — = h($account['unit']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Internal archive workspace
+
KBRI Harare Archive Desk
+
+
+ = h(role_badge_label($user['role'])) ?>
+ = h($user['name']) ?> · = h($user['username']) ?>
+ Sinkron UTC = h($todayUtc) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
Navigasi pohon
+
Menu utama
+
+
+
+
+
Klik tanda + untuk membuka sub-menu dan pilih folder terdalam sebagai lokasi arsip.
+
+ = render_tree_nodes(archive_tree(), $folderFilter, $user) ?>
+
+
+
+
+
+
+
+
+
First delivery
+
Workflow arsip harian dari input sampai detail.
+
Form di bawah terhubung ke database, mengikuti folder tree, menyimpan lampiran, dan menampilkan daftar arsip sesuai hak akses akun yang aktif.
+
+
+
+
+
+
+
+
+
Total arsip
+
= (int) $counts['total_records'] ?>
+
= $user['role'] === 'staf' ? 'Hanya milik akun ini' : 'Seluruh arsip yang terlihat' ?>
+
+
+
+
+
Lampiran aktif
+
= (int) $counts['total_files'] ?>
+
Siap diunduh dari detail arsip
+
+
+
+
+
Masuk hari ini
+
= (int) $counts['created_today'] ?>
+
Dokumen baru per = h($todayUtc) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
Daftar arsip
+
= $folderFilter ? 'Folder aktif' : 'Arsip terbaru' ?>
+
+
+
Reset
+
+
+
+
Filter: = h($folderFilter) ?>
+
+
+
+
Belum ada arsip.
+
Pilih folder dari tree lalu input dokumen pertama untuk mulai membangun database arsip.
+
+
+
+
+
+
+ = h($record['confidentiality']) ?>
+ #= (int) $record['id'] ?>
+
+ = h($record['title']) ?>
+ = h($record['reference_code']) ?> · = h($record['record_day']) ?>/= h($record['record_month']) ?>/= h($record['record_year']) ?>
+ = h($record['folder_path']) ?>
+
+
+
+
+
+
+
+
+
Hak akses aktif
+
Kontrol akun
+
+
+ Melihat, input, edit, unduh, dan hapus semua arsip.
+ Akses ke seluruh menu utama dan nested folder.
+
+ Melihat seluruh database yang tersedia di dashboard.
+ Edit hanya arsip dari unit = h($user['unit']) ?>, tanpa fitur hapus.
+
+ Input, edit, hapus, dan unduh arsip milik akun sendiri.
+ Folder input dibatasi ke unit = h($user['unit']) ?>.
+
+
+
+
+
+
+
-
-
- Page updated: = htmlspecialchars($now) ?> (UTC)
-
+
+
+
diff --git a/logout.php b/logout.php
new file mode 100644
index 0000000..3794e6c
--- /dev/null
+++ b/logout.php
@@ -0,0 +1,10 @@
+