diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..4612b01 --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,117 @@ +:root { + color-scheme: light; + --bg: #f7f7f6; + --surface: #ffffff; + --text: #161616; + --muted: #6b6b6b; + --border: #e5e5e5; + --accent: #111111; + --accent-soft: #f0f0f0; + --radius-sm: 6px; + --radius-md: 10px; + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 24px; +} + +body { + font-family: "Space Grotesk", "Segoe UI", Helvetica, Arial, sans-serif; + background: var(--bg); + color: var(--text); +} + +.navbar { + background: var(--surface) !important; +} + +.hero { + background: var(--surface); +} + +.section-head { + margin-bottom: var(--space-4); +} + +.card, +.table { + border-color: var(--border) !important; +} + +.card { + border-radius: var(--radius-md); +} + +.btn { + border-radius: var(--radius-sm); + font-weight: 600; +} + +.btn-dark { + background: var(--accent); + border-color: var(--accent); +} + +.btn-outline-secondary { + border-color: var(--border); + color: var(--text); +} + +.btn-outline-secondary:hover { + background: var(--accent-soft); + border-color: var(--border); + color: var(--text); +} + +.dropzone { + border: 1px dashed var(--border); + border-radius: var(--radius-md); + padding: var(--space-5); + background: var(--surface); + position: relative; + display: flex; + align-items: center; + justify-content: center; + min-height: 140px; + transition: border-color 0.2s ease, background 0.2s ease; +} + +.dropzone input[type="file"] { + position: absolute; + inset: 0; + opacity: 0; + cursor: pointer; +} + +.dropzone.is-dragover { + border-color: var(--text); + background: #fafafa; +} + +.dropzone-placeholder { + display: flex; + flex-direction: column; + gap: var(--space-2); + text-align: center; +} + +.table th { + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--muted); +} + +.badge { + border-radius: 999px; + font-weight: 500; +} + +.alert { + border-radius: var(--radius-md); +} + +footer { + background: var(--surface); +} diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..e1ecf63 --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,40 @@ +(() => { + const dropzones = document.querySelectorAll('[data-dropzone]'); + dropzones.forEach((dropzone) => { + const input = dropzone.querySelector('input[type="file"]'); + const fileLabel = dropzone.querySelector('[data-file-name]'); + if (!input || !fileLabel) return; + + const updateName = (file) => { + if (!file) { + fileLabel.textContent = 'Belum ada file dipilih'; + return; + } + fileLabel.textContent = `${file.name} (${Math.round(file.size / 1024)} KB)`; + }; + + input.addEventListener('change', (event) => { + updateName(event.target.files[0]); + }); + + ['dragenter', 'dragover'].forEach((eventName) => { + dropzone.addEventListener(eventName, (event) => { + event.preventDefault(); + dropzone.classList.add('is-dragover'); + }); + }); + + ['dragleave', 'drop'].forEach((eventName) => { + dropzone.addEventListener(eventName, () => { + dropzone.classList.remove('is-dragover'); + }); + }); + + dropzone.addEventListener('drop', (event) => { + event.preventDefault(); + if (!event.dataTransfer.files.length) return; + input.files = event.dataTransfer.files; + updateName(event.dataTransfer.files[0]); + }); + }); +})(); diff --git a/event.php b/event.php new file mode 100644 index 0000000..484b3e7 --- /dev/null +++ b/event.php @@ -0,0 +1,102 @@ +prepare('SELECT id, event_type, message, meta, created_at FROM sahur_events WHERE id = :id'); + $stmt->execute([':id' => $eventId]); + $event = $stmt->fetch(); +} + +$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ''; +$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; +?> + + + + + + Detail Aktivitas Sahur + + + + + + + + + + + + + + + + + + + +
+
+
+

Detail Aktivitas

+

Log Sahur

+
+ Kembali +
+ + + + +
+
+
+
+

Tipe

+

+
+
+

Waktu

+

WIB

+
+
+

ID Event

+

#

+
+
+

Pesan

+

+
+
+

Meta

+
+
+
+
+
+ +
+ + + + + + diff --git a/index.php b/index.php index 7205f3d..54e46cd 100644 --- a/index.php +++ b/index.php @@ -2,149 +2,360 @@ declare(strict_types=1); @ini_set('display_errors', '1'); @error_reporting(E_ALL); -@date_default_timezone_set('UTC'); +@date_default_timezone_set('Asia/Jakarta'); + +session_start(); +require_once __DIR__ . '/db/config.php'; + +$pdo = db(); +$pdo->exec( + 'CREATE TABLE IF NOT EXISTS sahur_settings (' + . 'id INT PRIMARY KEY, ' + . 'sahur_hour TINYINT NOT NULL, ' + . 'sahur_minute TINYINT NOT NULL, ' + . 'updated_at DATETIME NOT NULL' + . ')' +); +$pdo->exec( + 'CREATE TABLE IF NOT EXISTS sahur_events (' + . 'id INT AUTO_INCREMENT PRIMARY KEY, ' + . 'event_type VARCHAR(32) NOT NULL, ' + . 'message VARCHAR(255) NOT NULL, ' + . 'meta TEXT NULL, ' + . 'created_at DATETIME NOT NULL' + . ')' +); + +$settings = $pdo->query('SELECT id, sahur_hour, sahur_minute, updated_at FROM sahur_settings WHERE id = 1')->fetch(); +if (!$settings) { + $stmt = $pdo->prepare('INSERT INTO sahur_settings (id, sahur_hour, sahur_minute, updated_at) VALUES (1, :hour, :minute, NOW())'); + $stmt->execute([':hour' => 3, ':minute' => 0]); + $settings = ['id' => 1, 'sahur_hour' => 3, 'sahur_minute' => 0, 'updated_at' => date('Y-m-d H:i:s')]; +} + +$flash = $_SESSION['flash'] ?? null; +unset($_SESSION['flash']); + +function redirect_with_flash(string $type, string $message): void { + $_SESSION['flash'] = ['type' => $type, 'message' => $message]; + header('Location: /'); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = $_POST['action'] ?? ''; + if ($action === 'upload') { + if (!isset($_FILES['sahur_file']) || $_FILES['sahur_file']['error'] !== UPLOAD_ERR_OK) { + redirect_with_flash('danger', 'Upload gagal. Pilih file MP3 yang valid.'); + } + + $file = $_FILES['sahur_file']; + if ($file['size'] > 15000000) { + redirect_with_flash('danger', 'Ukuran file terlalu besar. Maksimum 15MB.'); + } + + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mime = $finfo ? finfo_file($finfo, $file['tmp_name']) : ''; + if ($finfo) { + finfo_close($finfo); + } + + $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + if ($mime !== 'audio/mpeg' || $ext !== 'mp3') { + redirect_with_flash('danger', 'Format file harus MP3 (audio/mpeg).'); + } + + $uploadDir = __DIR__ . '/uploads'; + if (!is_dir($uploadDir)) { + mkdir($uploadDir, 0775, true); + } + + $targetPath = $uploadDir . '/sahur.mp3'; + if (!move_uploaded_file($file['tmp_name'], $targetPath)) { + redirect_with_flash('danger', 'Upload gagal saat menyimpan file.'); + } + + $meta = json_encode([ + 'original_name' => $file['name'], + 'size' => $file['size'], + ]); + $stmt = $pdo->prepare('INSERT INTO sahur_events (event_type, message, meta, created_at) VALUES (:type, :message, :meta, NOW())'); + $stmt->execute([ + ':type' => 'upload', + ':message' => 'File sahur.mp3 diperbarui', + ':meta' => $meta, + ]); + + redirect_with_flash('success', 'File sahur.mp3 berhasil diunggah dan diganti.'); + } + + if ($action === 'set_schedule') { + $hour = filter_input(INPUT_POST, 'sahur_hour', FILTER_VALIDATE_INT); + $minute = filter_input(INPUT_POST, 'sahur_minute', FILTER_VALIDATE_INT); + + if ($hour === false || $minute === false || $hour < 0 || $hour > 23 || $minute < 0 || $minute > 59) { + redirect_with_flash('danger', 'Jam sahur tidak valid. Masukkan 0-23 untuk jam dan 0-59 untuk menit.'); + } + + $stmt = $pdo->prepare('UPDATE sahur_settings SET sahur_hour = :hour, sahur_minute = :minute, updated_at = NOW() WHERE id = 1'); + $stmt->execute([':hour' => $hour, ':minute' => $minute]); + + $meta = json_encode(['hour' => $hour, 'minute' => $minute]); + $stmt = $pdo->prepare('INSERT INTO sahur_events (event_type, message, meta, created_at) VALUES (:type, :message, :meta, NOW())'); + $stmt->execute([ + ':type' => 'schedule', + ':message' => 'Jadwal sahur diperbarui', + ':meta' => $meta, + ]); + + redirect_with_flash('success', 'Jadwal sahur berhasil diperbarui.'); + } + + if ($action === 'test_now') { + $stmt = $pdo->prepare('INSERT INTO sahur_events (event_type, message, meta, created_at) VALUES (:type, :message, :meta, NOW())'); + $stmt->execute([ + ':type' => 'test', + ':message' => 'Tes manual dipicu dari dashboard', + ':meta' => null, + ]); + + redirect_with_flash('success', 'Tes sahur dicatat. Integrasi bot akan memutar audio saat tersedia.'); + } +} + +$audioPath = __DIR__ . '/uploads/sahur.mp3'; +$fileExists = is_file($audioPath); +$fileSize = $fileExists ? filesize($audioPath) : null; +$fileUpdated = $fileExists ? date('Y-m-d H:i', filemtime($audioPath)) : null; + +$now = new DateTime('now'); +$nextRun = (clone $now)->setTime((int)$settings['sahur_hour'], (int)$settings['sahur_minute'], 0); +if ($nextRun <= $now) { + $nextRun->modify('+1 day'); +} + +$events = $pdo->query('SELECT id, event_type, message, created_at FROM sahur_events ORDER BY id DESC LIMIT 8')->fetchAll(); -$phpVersion = PHP_VERSION; -$now = date('Y-m-d H:i:s'); -?> - - - - - - New Style - + + + + + + Sahur Bot Control Center - - - - - + - - + + + -
-
-

Analyzing your requirements and generating your website…

-
- Loading… + + +
+
+
+
+

Dashboard Sahur

+

Kendalikan jadwal sahur dan audio bot dalam satu tempat.

+

Upload MP3, ubah jam sahur, dan catat tes manual. Status file dan jadwal berikutnya selalu terlihat jelas.

+ +
+
+
+
+

Next run

+

format('d M Y, H:i')) ?> WIB

+
+ (jadwal aktif) + +
+

Pastikan bot Discord kamu membaca jadwal yang sama.

+
+
+
+
+
+
+ +
+ + + + +
+
+

Status Saat Ini

+

Pantau kesiapan audio dan jadwal sahur berikutnya.

+
+
+
+
+
+

Audio sahur

+

+

+
+
+
+
+
+
+

Update terakhir

+

+

Penggantian file terakhir.

+
+
+
+
+
+
+

Next run

+

format('d M Y, H:i')) ?> WIB

+

Jadwal sahur berdasarkan setting saat ini.

+
+
+
+
+ + + +
+ +
+
+

Upload MP3 Sahur

+

Drag & drop atau klik untuk memilih file. File akan ditimpa menjadi sahur.mp3.

+
+
+
+
+ +
+
+ +
+ Tarik file ke sini + atau klik untuk memilih + Belum ada file dipilih +
+
+
+
+ +
+
+
+
+
+ +
+
+

Jadwal Sahur

+

Atur jam sahur harian yang akan digunakan bot.

+
+
+
+
+ +
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+

Log Aktivitas

+

Riwayat upload, perubahan jadwal, dan tes manual.

+
+
+
+ +

Belum ada aktivitas. Mulai dengan upload file sahur pertama.

+ +
+ + + + + + + + + + + + + + + + + + + +
WaktuTipePesan
Detail
+
+ +
+
+
-