diff --git a/assets/css/custom.css b/assets/css/custom.css index 789132e..f2d77a4 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,403 +1,196 @@ +:root { + --bg: #f6f7f9; + --surface: #ffffff; + --surface-muted: #f1f3f6; + --border: #e2e6ea; + --text: #0f172a; + --muted: #6b7280; + --accent: #111827; + --accent-soft: #e5e7eb; + --success: #0f766e; + --warning: #b45309; + --info: #1d4ed8; + --danger: #b91c1c; + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --shadow-sm: 0 6px 16px rgba(15, 23, 42, 0.06); +} + +* { + box-sizing: border-box; +} + 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; + background: var(--bg); + color: var(--text); + font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-size: 14px; + margin: 0; + min-height: 100vh; } -.main-wrapper { - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - width: 100%; - padding: 20px; - box-sizing: border-box; - position: relative; - z-index: 1; +a { + color: var(--text); + text-decoration: none; } -@keyframes gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } +a:hover { + color: #000; } -.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; +.navbar { + background: var(--surface); + border-bottom: 1px solid var(--border); } -.chat-header { - padding: 1.5rem; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - background: rgba(255, 255, 255, 0.5); - font-weight: 700; - font-size: 1.1rem; - display: flex; - justify-content: space-between; - align-items: center; +.navbar-brand { + font-weight: 600; + letter-spacing: 0.2px; } -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1.25rem; +.nav-link { + color: var(--muted); + font-weight: 500; } -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 6px; +.nav-link.active, +.nav-link:hover { + color: var(--text); } -::-webkit-scrollbar-track { - background: transparent; +.hero { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 24px; + box-shadow: var(--shadow-sm); } -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 10px; +.card { + border-radius: var(--radius-md); + border: 1px solid var(--border); + box-shadow: none; } -::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.5); +.card-header { + background: var(--surface); + border-bottom: 1px solid var(--border); + font-weight: 600; } -.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); +.card-muted { + background: var(--surface-muted); + border-radius: var(--radius-md); + padding: 16px; } -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px) scale(0.95); } - to { opacity: 1; transform: translateY(0) scale(1); } +.section-title { + font-size: 15px; + font-weight: 600; + letter-spacing: 0.2px; } -.message.visitor { - align-self: flex-end; - background: linear-gradient(135deg, #212529 0%, #343a40 100%); - color: #fff; - border-bottom-right-radius: 4px; +.badge-soft { + border-radius: 999px; + font-weight: 500; + padding: 6px 10px; + font-size: 12px; + border: 1px solid transparent; + display: inline-flex; + align-items: center; + gap: 6px; } -.message.bot { - align-self: flex-start; - background: #ffffff; - color: #212529; - border-bottom-left-radius: 4px; +.badge-soft.success { + color: var(--success); + border-color: rgba(15, 118, 110, 0.2); + background: rgba(15, 118, 110, 0.08); } -.chat-input-area { - padding: 1.25rem; - background: rgba(255, 255, 255, 0.5); - border-top: 1px solid rgba(0, 0, 0, 0.05); +.badge-soft.warning { + color: var(--warning); + border-color: rgba(180, 83, 9, 0.2); + background: rgba(180, 83, 9, 0.08); } -.chat-input-area form { - display: flex; - gap: 0.75rem; +.badge-soft.info { + color: var(--info); + border-color: rgba(29, 78, 216, 0.2); + background: rgba(29, 78, 216, 0.08); } -.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; +.badge-soft.danger { + color: var(--danger); + border-color: rgba(185, 28, 28, 0.2); + background: rgba(185, 28, 28, 0.08); } -.chat-input-area input:focus { - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2); +.btn-primary { + background: var(--accent); + border-color: var(--accent); + border-radius: var(--radius-sm); + padding: 8px 14px; + font-weight: 600; } -.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; +.btn-outline-secondary { + border-radius: var(--radius-sm); + padding: 8px 14px; + font-weight: 600; } -.chat-input-area button:hover { - background: #000; - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0,0,0,0.2); +.btn-light { + border-radius: var(--radius-sm); + padding: 8px 14px; } -/* Background Animations */ -.bg-animations { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - overflow: hidden; - pointer-events: none; -} - -.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; +.form-control, +.form-select { + border-radius: var(--radius-sm); + border-color: var(--border); + font-size: 14px; + padding: 8px 10px; } .table { - width: 100%; - border-collapse: separate; - border-spacing: 0 8px; - margin-top: 1.5rem; + font-size: 13px; } -.table th { - background: transparent; - border: none; - padding: 1rem; - color: #6c757d; - font-weight: 600; - text-transform: uppercase; - font-size: 0.75rem; - letter-spacing: 1px; +.table thead th { + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--muted); } -.table td { - background: #fff; - padding: 1rem; - border: none; +.toast { + border-radius: var(--radius-md); + box-shadow: var(--shadow-sm); } -.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; +.footer { + color: var(--muted); + font-size: 12px; + padding: 24px 0 40px; } -.form-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: 600; - font-size: 0.9rem; +.empty-state { + padding: 28px; + border: 1px dashed var(--border); + border-radius: var(--radius-md); + color: var(--muted); + background: var(--surface); } -.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; +.tag { + display: inline-flex; + align-items: center; + gap: 6px; + background: var(--accent-soft); + color: var(--text); + border-radius: 999px; + padding: 4px 10px; + font-size: 12px; + font-weight: 500; } - -.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; -} - -.btn-delete { - background: #dc3545; - color: white; - border: none; - padding: 0.25rem 0.5rem; - border-radius: 4px; - cursor: pointer; -} - -.btn-add { - background: #212529; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; - margin-top: 1rem; -} - -.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; -} - -.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..20327a0 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,39 +1,9 @@ document.addEventListener('DOMContentLoaded', () => { - const chatForm = document.getElementById('chat-form'); - const chatInput = document.getElementById('chat-input'); - const chatMessages = document.getElementById('chat-messages'); - - const appendMessage = (text, sender) => { - const msgDiv = document.createElement('div'); - msgDiv.classList.add('message', sender); - msgDiv.textContent = text; - chatMessages.appendChild(msgDiv); - chatMessages.scrollTop = chatMessages.scrollHeight; - }; - - chatForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const message = chatInput.value.trim(); - if (!message) return; - - 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'); - } + const toastElList = document.querySelectorAll('.toast'); + if (toastElList.length > 0 && typeof bootstrap !== 'undefined') { + toastElList.forEach((toastEl) => { + const toast = new bootstrap.Toast(toastEl); + toast.show(); }); + } }); diff --git a/db/migrations/20260306_001_letters.sql b/db/migrations/20260306_001_letters.sql new file mode 100644 index 0000000..4a647d8 --- /dev/null +++ b/db/migrations/20260306_001_letters.sql @@ -0,0 +1,28 @@ +CREATE TABLE IF NOT EXISTS surat_masuk ( + id INT AUTO_INCREMENT PRIMARY KEY, + nomor_surat VARCHAR(100) NULL, + tanggal_surat DATE NOT NULL, + pengirim VARCHAR(150) NOT NULL, + perihal VARCHAR(200) NOT NULL, + departemen_tujuan VARCHAR(120) NOT NULL, + status ENUM('baru','diproses','selesai') NOT NULL DEFAULT 'baru', + file_path VARCHAR(255) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS surat_keluar ( + id INT AUTO_INCREMENT PRIMARY KEY, + nomor_surat VARCHAR(120) NOT NULL, + kode_surat VARCHAR(30) NOT NULL, + urut INT NOT NULL, + bulan INT NOT NULL, + tahun INT NOT NULL, + tanggal_surat DATE NOT NULL, + tujuan VARCHAR(150) NOT NULL, + perihal VARCHAR(200) NOT NULL, + departemen_pengirim VARCHAR(120) NOT NULL, + status ENUM('draft','review','approved','kirim') NOT NULL DEFAULT 'draft', + file_path VARCHAR(255) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uniq_nomor (nomor_surat) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/includes/db_init.php b/includes/db_init.php new file mode 100644 index 0000000..b466ac2 --- /dev/null +++ b/includes/db_init.php @@ -0,0 +1,19 @@ +exec($sql); + } + } + $initialized = true; +} diff --git a/includes/helpers.php b/includes/helpers.php new file mode 100644 index 0000000..a46c6b3 --- /dev/null +++ b/includes/helpers.php @@ -0,0 +1,35 @@ + 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII']; + return $roman[$month] ?? ''; +} + +function format_nomor(string $kode, int $urut, int $bulan, int $tahun): string { + $kode = strtoupper(trim($kode)); + $urutFormatted = str_pad((string)$urut, 3, '0', STR_PAD_LEFT); + return $kode . '/' . $urutFormatted . '/' . roman_month($bulan) . '/' . $tahun; +} + +function status_badge(string $status, string $type): string { + $map = [ + 'masuk' => [ + 'baru' => 'info', + 'diproses' => 'warning', + 'selesai' => 'success', + ], + 'keluar' => [ + 'draft' => 'info', + 'review' => 'warning', + 'approved' => 'success', + 'kirim' => 'success', + ], + ]; + $key = $map[$type][$status] ?? 'info'; + return 'badge-soft ' . $key; +} diff --git a/includes/layout.php b/includes/layout.php new file mode 100644 index 0000000..513240c --- /dev/null +++ b/includes/layout.php @@ -0,0 +1,84 @@ + + + +
+ + ++ Kelola surat masuk & surat keluar lintas departemen dengan nomor otomatis, status yang jelas, + dan arsip yang mudah ditelusuri. +
+ -= ($_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) ?>
| Tanggal | +Pengirim | +Perihal | +Status | +
|---|---|---|---|
| = h($row['tanggal_surat']) ?> | += h($row['pengirim']) ?> | ++ + = h($row['perihal']) ?> + + | += h($row['status']) ?> | +
| Tanggal | +Nomor | +Perihal | +Status | +
|---|---|---|---|
| = h($row['tanggal_surat']) ?> | ++ + = h($row['nomor_surat']) ?> + + | += h($row['perihal']) ?> | += h($row['status']) ?> | +
| Tanggal | +Nomor | +Tujuan | +Perihal | +Departemen | +Status | +
|---|---|---|---|---|---|
| = h($row['tanggal_surat']) ?> | ++ + = h($row['nomor_surat']) ?> + + | += h($row['tujuan']) ?> | += h($row['perihal']) ?> | += h($row['departemen_pengirim']) ?> | += h($row['status']) ?> | +
| Tanggal | +Nomor | +Pengirim | +Perihal | +Departemen | +Status | +
|---|---|---|---|---|---|
| = h($row['tanggal_surat']) ?> | += h($row['nomor_surat'] ?? '-') ?> | += h($row['pengirim']) ?> | ++ + = h($row['perihal']) ?> + + | += h($row['departemen_tujuan']) ?> | += h($row['status']) ?> | +