From fb3bcc656508f0e2b2b1a1726751619262d5748f Mon Sep 17 00:00:00 2001 From: Medicone1 Date: Fri, 8 Aug 2025 15:16:48 +0700 Subject: [PATCH] first commit --- admin.html | 155 ++++++++++++ images/Denah2.png | Bin 0 -> 27506 bytes images/suv-car.png | Bin 0 -> 11343 bytes index.html | 74 ++++++ script.js | 577 +++++++++++++++++++++++++++++++++++++++++++++ style.css | 236 ++++++++++++++++++ 6 files changed, 1042 insertions(+) create mode 100644 admin.html create mode 100644 images/Denah2.png create mode 100644 images/suv-car.png create mode 100644 index.html create mode 100644 script.js create mode 100644 style.css diff --git a/admin.html b/admin.html new file mode 100644 index 0000000..eeae48b --- /dev/null +++ b/admin.html @@ -0,0 +1,155 @@ + + + + + + Dashboard Admin - Monitoring Parkir + + + + + + +
+
+

Dashboard Admin - Monitoring Parkir

+
+ +
+
+

Status Parkir Saat Ini

+
+ Logo Kendaraan +

Parkir Slot 1

+

Tersedia

+
+
+ Logo Kendaraan +

Parkir Slot 2

+

Tersedia

+
+
+ Logo Kendaraan +

Parkir Slot 3

+

Tersedia

+
+
+
+

Riwayat Parkir Realtime

+
+
+ πŸ”„ Memuat riwayat parkir... +
+
+
+
+
+ + \ No newline at end of file diff --git a/images/Denah2.png b/images/Denah2.png new file mode 100644 index 0000000000000000000000000000000000000000..8d7fab49ada5c9f95e2063235d879d0c4ec0d36f GIT binary patch literal 27506 zcmcG#bx@mMv;~^r?k+)!wgrk?aCa#Mic2Z(P=W_{*W#{4ixdx~6f5oy#XUufl^1^Z z-kCS=|2GWGkbK{fefHUBt+n@w(o|Q##iGIj006j3in7`O01%A$mBv6reB!`i-U$g{Q3Jd*gFvQP<3>)k!aqmOI(0~6*QfxG+{r9;g8w^l4@S5LOQywS}M1wfszrlKg zbxDEs26n`)G73u7f@(p5pk?G(q*x@ea3mbK&oAw-=vE|VBxcZoYd|reSTdmyfZPfC zI&&;ViAsTrXj<(5|E4uvVAR|nlWC(!3AHd&va+a%@YvNN9R){}?3UEZZQT82d}H9U zB+M4m8gO^}I=$@c^?Uc$t}s)p?mZ!z${kmcBX^Z)#ZETm8J*-%X$EaQJq6WiCM#xU zrq5qkY4*Spxm1s))AnZ?KEKvH_os%p+V2&S@5o3=lkPrbP4Sb(BrPo&XzOSzOAw

D{qSV-(H`uuF=9B#q*Ex+=p>t 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

X{1&tx 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 + + + + + Monitoring Slot Parkir + + + + + +

+ +
+
+

Monitoring Slot Parkir

+ +
+ Logo Kendaraan +

Parkir Slot 1

+

Status: Tersedia

+
+ +
+ Logo Kendaraan +

Parkir Slot 2

+

Status: Tersedia

+
+ +
+ Logo Kendaraan +

Parkir Slot 3

+

Status: Tersedia

+
+ +
+

Denah Lokasi Parkir

+
+ Denah Lokasi Parkir +
1
+
2
+
3
+
+ +
+
+
+ Tersedia +
+
+
+ Terisi +
+
+ +
+

Notes: Tempat parkir berada di sebelah kanan, dekat dengan palang parkir.

+
+
+
+ + + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..8b375a9 --- /dev/null +++ b/script.js @@ -0,0 +1,577 @@ +// FIREBASE KONFIGURASI +import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-app.js"; +import { getDatabase, ref, set, onValue, push } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-database.js"; + +// Firebase Configuration +const firebaseConfig = { + apiKey: "AIzaSyAS1qxaUCFRZ1Fe25qGK27JtwCWEPuaYys", + authDomain: "parkingslot-c5fba.firebaseapp.com", + databaseURL: "https://parkingslot-c5fba-default-rtdb.firebaseio.com", + projectId: "parkingslot-c5fba", + storageBucket: "parkingslot-c5fba.firebasestorage.app", + messagingSenderId: "675813222621", + appId: "1:675813222621:web:2986b33a1e5fd10c5b9334" +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); +const db = getDatabase(app); +console.log("Firebase berhasil terhubung:", db); + +// Global Variables +let isAdminMode = false; +let previousSlotStatus = { + slot_1: null, // null berarti belum pernah di-set + slot_2: null, + slot_3: null +}; +let slotStatus = { + slot_1: "Tersedia", + slot_2: "Tersedia", + slot_3: "Tersedia" +}; +let isSystemInitialized = false; + +// Set isAdminMode true jika di halaman admin.html +if (window.location.pathname.includes('admin.html')) { + isAdminMode = true; + console.log("Mode Admin aktif"); +} else { + console.log("Mode User aktif"); +} + +// Initialize previous status dari Firebase saat startup +function initializePreviousStatus() { + console.log("πŸ”„ Menginisialisasi status sebelumnya dari Firebase..."); + + return new Promise((resolve, reject) => { + let loadedSlots = 0; + const totalSlots = 3; + + // Load status sebelumnya dari Firebase untuk setiap slot + for (let i = 1; i <= 3; i++) { + const slotKey = `slot_${i}`; + + onValue(ref(db, `parkir/slot_${i}`), function(snapshot) { + const data = snapshot.val(); + if (data && data.status) { + previousSlotStatus[slotKey] = data.status; + console.log(`βœ… Status awal slot ${i}: ${data.status}`); + } else { + // Jika tidak ada data, set default + previousSlotStatus[slotKey] = "Tersedia"; + console.log(`⚠️ Slot ${i} tidak ada data, set default: Tersedia`); + } + + loadedSlots++; + if (loadedSlots === totalSlots) { + isSystemInitialized = true; + console.log("βœ… Semua slot berhasil diinisialisasi"); + console.log("Previous Status:", previousSlotStatus); + resolve(); + } + }, { onlyOnce: true }); + } + + // Timeout fallback jika Firebase lambat + setTimeout(() => { + if (!isSystemInitialized) { + console.log("⏰ Timeout inisialisasi, menggunakan default values"); + previousSlotStatus = { + slot_1: "Tersedia", + slot_2: "Tersedia", + slot_3: "Tersedia" + }; + isSystemInitialized = true; + resolve(); + } + }, 5000); + }); +} + +// Setup MQTT +const client = mqtt.connect('ws://broker.hivemq.com:8000/mqtt'); +client.on('connect', function () { + console.log("πŸ”— Terhubung ke Broker MQTT"); + client.subscribe('parkir/slot_1'); + client.subscribe('parkir/slot_2'); + client.subscribe('parkir/slot_3'); + client.subscribe('parkir/status'); + + // Initialize previous status setelah MQTT connect + setTimeout(async () => { + await initializePreviousStatus(); + console.log("πŸš€ Sistem siap menerima data MQTT"); + }, 1000); +}); + +client.on('message', function (topic, message) { + // Jangan proses jika sistem belum diinisialisasi + if (!isSystemInitialized) { + console.log("⏳ Sistem belum siap, mengabaikan pesan MQTT"); + return; + } + + console.log(`πŸ“¨ Pesan MQTT diterima: ${message.toString()} di topik: ${topic}`); + const distance = parseInt(message.toString()); + + if (isNaN(distance)) { + console.error('❌ Data tidak valid diterima:', message.toString()); + return; + } + + // Process each slot + if (topic === 'parkir/slot_1') { + processSlotUpdate(1, distance, 'mqtt'); + } + if (topic === 'parkir/slot_2') { + processSlotUpdate(2, distance, 'mqtt'); + } + if (topic === 'parkir/slot_3') { + processSlotUpdate(3, distance, 'mqtt'); + } + + checkParkingStatus(); +}); + +// Fungsi utama untuk memproses update slot - DIPERBAIKI +function processSlotUpdate(slotNumber, distance, source = 'unknown') { + const newStatus = distance > 5 ? "Tersedia" : "Terisi"; + const slotKey = `slot_${slotNumber}`; + + console.log(`πŸ”„ Processing slot ${slotNumber}: distance=${distance}, newStatus=${newStatus}, source=${source}`); + console.log(`πŸ“Š Previous status slot ${slotNumber}: ${previousSlotStatus[slotKey]}`); + + // Update status slot di UI + updateSlotStatus(slotNumber, distance); + + // Cek perubahan status - LOGIKA DIPERBAIKI + const prevStatus = previousSlotStatus[slotKey]; + + // Jika ini adalah data pertama (previous status null), langsung log + if (prevStatus === null) { + console.log(`πŸ†• DATA PERTAMA - Slot ${slotNumber}: null -> ${newStatus}`); + logParkingHistory(slotNumber, newStatus, isAdminMode, source); + previousSlotStatus[slotKey] = newStatus; + } + // Jika ada perubahan status dari status sebelumnya + else if (prevStatus !== newStatus) { + console.log(`πŸ”₯ PERUBAHAN TERDETEKSI - Slot ${slotNumber}: ${prevStatus} -> ${newStatus}`); + console.log(`πŸ“ Mode saat ini: ${isAdminMode ? 'Admin' : 'User'}`); + + // SELALU log parking history baik di user maupun admin mode + logParkingHistory(slotNumber, newStatus, isAdminMode, source); + + // Update previous status SETELAH logging + previousSlotStatus[slotKey] = newStatus; + console.log(`βœ… Previous status slot ${slotNumber} diupdate ke: ${newStatus}`); + } else { + console.log(`➑️ Tidak ada perubahan status slot ${slotNumber}: ${newStatus}`); + } + + // Update current status + slotStatus[slotKey] = newStatus; +} + +// Fungsi untuk update indikator pada denah +function updateMapIndicator(slot, status) { + const indicator = document.getElementById('indicator_' + slot); + if (indicator) { + indicator.classList.remove('available', 'occupied'); + if (status === "Tersedia") { + indicator.classList.add('available'); + indicator.style.backgroundColor = '#4CAF50'; + } else { + indicator.classList.add('occupied'); + indicator.style.backgroundColor = '#f44336'; + } + indicator.title = `Slot ${slot} - ${status}`; + console.log(`πŸ—ΊοΈ Indikator denah slot ${slot} diupdate: ${status}`); + } +} + +function updateSlotStatus(slot, distance) { + const statusElement = document.getElementById('status_' + slot); + const slotElement = document.getElementById('sensor_' + slot); + + let status; + if (distance > 5) { + status = "Tersedia"; + if (statusElement) { + statusElement.textContent = "Status: Tersedia"; + statusElement.className = "status empty"; + } + if (slotElement) { + slotElement.style.backgroundColor = "lightgreen"; + } + } else { + status = "Terisi"; + if (statusElement) { + statusElement.textContent = "Status: Terisi"; + statusElement.className = "status occupied"; + } + if (slotElement) { + slotElement.style.backgroundColor = "lightcoral"; + } + } + + updateMapIndicator(slot, status); + // Simpan ke Firebase tanpa memanggil processSlotUpdate lagi untuk menghindari loop + updateFirebaseStatusOnly(slot, distance); +} + +function checkParkingStatus() { + const allFull = slotStatus.slot_1 === "Terisi" && slotStatus.slot_2 === "Terisi" && slotStatus.slot_3 === "Terisi"; + if (allFull) { + console.log("🚫 Parkir Penuh - Palang tetap tertutup."); + client.publish('palang/status', 'Tutup'); + } else { + console.log("βœ… Ada slot tersedia - Palang dibuka."); + client.publish('palang/status', 'Buka'); + } +} + +// Fungsi khusus untuk update Firebase tanpa trigger logging +function updateFirebaseStatusOnly(slot, distance) { + const status = distance > 5 ? "Tersedia" : "Terisi"; + console.log(`πŸ’Ύ Menyimpan ke Firebase - Slot ${slot}: distance=${distance}, status=${status}`); + + set(ref(db, 'parkir/slot_' + slot), { + distance: distance, + status: status, + lastUpdate: new Date().toISOString(), + updatedBy: isAdminMode ? 'admin' : 'user' + }) + .then(() => { + console.log(`βœ… Status slot ${slot} berhasil disimpan ke Firebase`); + }) + .catch((error) => { + console.error("❌ Gagal menyimpan status ke Firebase:", error); + }); +} + +// Fungsi log parking history - DIPERBAIKI DENGAN LOGGING DETAIL +function logParkingHistory(slot, status, isAdmin, source = 'unknown') { + const timestamp = new Date().toLocaleString('id-ID'); + const historyData = { + slot: slot, + status: status, + timestamp: timestamp, + date: new Date().toISOString(), + source: isAdmin ? 'admin' : 'user', + dataSource: source, // mqtt, firebase, manual + sessionId: generateSessionId() + }; + + console.log(`πŸ“Š MENYIMPAN RIWAYAT PARKIR:`); + console.log(` - Slot: ${slot}`); + console.log(` - Status: ${status}`); + console.log(` - Mode: ${historyData.source}`); + console.log(` - Data Source: ${source}`); + console.log(` - Timestamp: ${timestamp}`); + + push(ref(db, 'parking_history'), historyData) + .then(() => { + console.log(`βœ… SUKSES! Riwayat parkir slot ${slot} tersimpan (${historyData.source})`); + + // Hanya update tampilan jika di mode admin + if (isAdmin && typeof updateHistoryDisplay === 'function') { + console.log(`πŸ”„ Mengupdate tampilan riwayat admin...`); + updateHistoryDisplay(); + } + }) + .catch((error) => { + console.error(`❌ GAGAL! Menyimpan riwayat parkir slot ${slot}:`, error); + }); +} + +// Generate session ID untuk tracking +function generateSessionId() { + return Date.now() + '_' + Math.random().toString(36).substr(2, 9); +} + +// Fungsi untuk update tampilan riwayat (hanya di admin mode) +function updateHistoryDisplay() { + // Hanya jalankan jika di admin mode + if (!isAdminMode) { + console.log("updateHistoryDisplay diabaikan - bukan mode admin"); + return; + } + + onValue(ref(db, 'parking_history'), function(snapshot) { + const historyContainer = document.getElementById('history-container'); + if (!historyContainer) { + console.log("Element history-container tidak ditemukan"); + return; + } + + const data = snapshot.val(); + if (!data) { + historyContainer.innerHTML = '
πŸ“ Belum ada riwayat parkir
'; + return; + } + + const historyArray = Object.values(data).sort((a, b) => new Date(b.date) - new Date(a.date)); + let historyHTML = '
'; + + historyArray.forEach(record => { + const statusClass = record.status === 'Terisi' ? 'entry' : 'exit'; + const statusText = record.status === 'Terisi' ? 'Masuk' : 'Keluar'; + const sourceIcon = record.source === 'admin' ? 'πŸ”§' : 'πŸ‘€'; + const dataSourceIcon = record.dataSource === 'mqtt' ? 'πŸ“‘' : 'πŸ’Ύ'; + + historyHTML += ` +
+
+ Slot ${record.slot} - ${statusText} + ${sourceIcon} ${record.source} ${dataSourceIcon} ${record.dataSource || 'unknown'} +
+
${record.timestamp}
+
+ `; + }); + + historyHTML += '
'; + historyContainer.innerHTML = historyHTML; + console.log(`πŸ“‹ Tampilan riwayat diupdate dengan ${historyArray.length} record`); + }); +} + +// Listener realtime Firebase untuk semua slot - DIPERBAIKI +function setupRealtimeListeners() { + console.log("πŸ”₯ Setting up Firebase realtime listeners..."); + + // Listener untuk slot 1 + onValue(ref(db, 'parkir/slot_1'), function(snapshot) { + const data = snapshot.val(); + if (data) { + console.log(`πŸ”₯ Firebase realtime update slot 1: distance=${data.distance}, status=${data.status}`); + + // Hanya update UI, jangan trigger processSlotUpdate untuk menghindari double logging + const statusElement = document.getElementById('status_1'); + const slotElement = document.getElementById('sensor_1'); + + if (data.distance > 5) { + if (statusElement) { + statusElement.textContent = "Status: Tersedia"; + statusElement.className = "status empty"; + } + if (slotElement) { + slotElement.style.backgroundColor = "lightgreen"; + } + } else { + if (statusElement) { + statusElement.textContent = "Status: Terisi"; + statusElement.className = "status occupied"; + } + if (slotElement) { + slotElement.style.backgroundColor = "lightcoral"; + } + } + + updateMapIndicator(1, data.status); + slotStatus.slot_1 = data.status; + } + }); + + // Listener untuk slot 2 + onValue(ref(db, 'parkir/slot_2'), function(snapshot) { + const data = snapshot.val(); + if (data) { + console.log(`πŸ”₯ Firebase realtime update slot 2: distance=${data.distance}, status=${data.status}`); + + const statusElement = document.getElementById('status_2'); + const slotElement = document.getElementById('sensor_2'); + + if (data.distance > 5) { + if (statusElement) { + statusElement.textContent = "Status: Tersedia"; + statusElement.className = "status empty"; + } + if (slotElement) { + slotElement.style.backgroundColor = "lightgreen"; + } + } else { + if (statusElement) { + statusElement.textContent = "Status: Terisi"; + statusElement.className = "status occupied"; + } + if (slotElement) { + slotElement.style.backgroundColor = "lightcoral"; + } + } + + updateMapIndicator(2, data.status); + slotStatus.slot_2 = data.status; + } + }); + + // Listener untuk slot 3 + onValue(ref(db, 'parkir/slot_3'), function(snapshot) { + const data = snapshot.val(); + if (data) { + console.log(`πŸ”₯ Firebase realtime update slot 3: distance=${data.distance}, status=${data.status}`); + + const statusElement = document.getElementById('status_3'); + const slotElement = document.getElementById('sensor_3'); + + if (data.distance > 5) { + if (statusElement) { + statusElement.textContent = "Status: Tersedia"; + statusElement.className = "status empty"; + } + if (slotElement) { + slotElement.style.backgroundColor = "lightcoral"; + } + } else { + if (statusElement) { + statusElement.textContent = "Status: Terisi"; + statusElement.className = "status occupied"; + } + if (slotElement) { + slotElement.style.backgroundColor = "lightcoral"; + } + } + + updateMapIndicator(3, data.status); + slotStatus.slot_3 = data.status; + } + }); + + console.log("βœ… Realtime listeners untuk Firebase telah diatur"); +} + +// Mode switching functions (Admin login modal etc.) +window.showAdminLogin = function () { + const modal = document.getElementById('admin-modal'); + if (modal) modal.style.display = 'flex'; +} + +window.closeAdminModal = function () { + const modal = document.getElementById('admin-modal'); + const input = document.getElementById('admin-password'); + if (modal) modal.style.display = 'none'; + if (input) input.value = ''; +} + +window.loginAdmin = function () { + const input = document.getElementById('admin-password'); + if (input && input.value === 'admin123') { + window.location.href = 'admin.html'; + } else { + alert('Password salah!'); + } +} + +window.handlePasswordKeypress = function (event) { + if (event.key === 'Enter') { + loginAdmin(); + } +} + +// Update admin status display +function updateAdminStatus() { + if (isAdminMode) { + for (let i = 1; i <= 3; i++) { + const userStatus = document.getElementById('status_' + i)?.textContent; + const adminElement = document.getElementById('admin-status-' + i); + if (adminElement && userStatus) { + adminElement.textContent = userStatus.replace('Status: ', ''); + adminElement.style.color = userStatus.includes('Tersedia') ? '#4CAF50' : '#f44336'; + adminElement.style.fontWeight = 'bold'; + } + } + } +} + +// FUNGSI DEBUG YANG DITINGKATKAN +window.debugParkingSystem = function() { + console.log("=== πŸ” DEBUG PARKING SYSTEM ==="); + console.log("Mode:", isAdminMode ? "Admin" : "User"); + console.log("System Initialized:", isSystemInitialized); + console.log("Current Slot Status:", slotStatus); + console.log("Previous Slot Status:", previousSlotStatus); + console.log("Firebase DB Connected:", !!db); + console.log("MQTT Client Connected:", client.connected); + console.log("==============================="); +} + +// Fungsi untuk testing manual - DIPERBAIKI +window.testHistoryLogging = function(slot, status) { + console.log(`πŸ§ͺ MANUAL TEST - Logging history for slot ${slot} with status ${status}`); + + // Simulasi perubahan status + const slotKey = `slot_${slot}`; + const currentPrev = previousSlotStatus[slotKey]; + + console.log(`πŸ“Š Before test - Previous: ${currentPrev}, New: ${status}`); + + // Force log tanpa cek perubahan + logParkingHistory(slot, status, isAdminMode, 'manual-test'); + + // Update previous status + previousSlotStatus[slotKey] = status; + console.log(`βœ… Test completed - Previous status updated to: ${status}`); +} + +// FUNGSI KHUSUS UNTUK FORCE LOGGING (Testing) +window.forceLogHistory = function(slot, status) { + console.log(`🚨 FORCE LOGGING - Slot ${slot}, Status: ${status}`); + logParkingHistory(slot, status, isAdminMode, 'force-test'); +} + +// Inisialisasi saat DOM ready +window.addEventListener('DOMContentLoaded', function() { + console.log(`πŸš€ Sistem parkir dimulai - Mode: ${isAdminMode ? 'πŸ”§ Admin' : 'πŸ‘€ User'}`); + + // Inisialisasi indikator denah + for (let i = 1; i <= 3; i++) { + updateMapIndicator(i, "Tersedia"); + } + console.log("πŸ—ΊοΈ Indikator denah telah diinisialisasi"); + + // Setup realtime listeners + setupRealtimeListeners(); + + // Jika admin mode, setup tampilan riwayat dan update berkala + if (isAdminMode) { + console.log("πŸ”§ Mengaktifkan fitur admin..."); + updateHistoryDisplay(); + + // Update admin status setiap detik + setInterval(updateAdminStatus, 1000); + + console.log("βœ… Admin mode: Pemantauan riwayat realtime aktif"); + } else { + console.log("πŸ‘€ User mode: Siap mengirim data riwayat ke Firebase"); + + // Test functions untuk user mode + window.testUserMode = function() { + console.log("πŸ§ͺ Testing user mode logging..."); + console.log("πŸ” Current state:", { + initialized: isSystemInitialized, + previousStatus: previousSlotStatus, + currentStatus: slotStatus + }); + + // Test dengan force logging + forceLogHistory(1, "Terisi"); + setTimeout(() => forceLogHistory(1, "Tersedia"), 3000); + }; + + // Fungsi untuk simulasi sensor + window.simulateSensor = function(slot, distance) { + console.log(`🎯 Simulasi sensor slot ${slot} dengan jarak ${distance}cm`); + processSlotUpdate(slot, distance, 'simulation'); + }; + } + + console.log("βœ… Inisialisasi sistem parkir selesai"); + console.log("πŸ’‘ Fungsi debugging:"); + console.log(" - debugParkingSystem() : Info sistem"); + console.log(" - forceLogHistory(slot, status) : Force log history"); + if (!isAdminMode) { + console.log(" - testUserMode() : Test user mode logging"); + console.log(" - simulateSensor(slot, distance) : Simulasi sensor"); + } +}); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..089d9d3 --- /dev/null +++ b/style.css @@ -0,0 +1,236 @@ +body { + font-family: Arial, sans-serif; + background-color: #f4f4f9; + text-align: center; + padding: 20px; + margin: 0; +} +h1 { + color: #4CAF50; + font-size: 2.5em; + margin-bottom: 30px; +} +.mode-buttons { + margin-bottom: 30px; +} +.mode-btn { + background-color: #4CAF50; + color: white; + border: none; + padding: 12px 24px; + margin: 0 10px; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s; +} +.mode-btn:hover { + background-color: #45a049; +} +.sensor { + background-color: #fff; + border-radius: 8px; + margin: 20px; + padding: 20px; + display: inline-block; + width: 250px; + position: relative; + transition: background-color 0.3s ease; +} +.sensor h3 { + margin-top: 0; + font-size: 1.2em; +} +.sensor img { + position: absolute; + top: 20px; + left: 1px; + width: 80px; + height: 80px; +} +.status { + font-weight: bold; + padding: 10px; + margin-top: 10px; + border-radius: 5px; + font-size: 1.1em; +} +.status.empty { + background-color: lightgreen; + color: white; +} +.status.occupied { + background-color: lightcoral; + color: white; +} +p { + font-size: 1.1em; + margin: 0; +} +.map-container { + margin-top: 40px; + text-align: center; +} +.map-container img { + width: 80%; + max-width: 600px; + height: auto; +} +.direction { + margin-top: 10px; + font-size: 1.2em; + color: #333; +} +/* Modal styles */ +.modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); + justify-content: center; + align-items: center; +} +.modal-content { + background-color: white; + padding: 30px; + border-radius: 10px; + text-align: center; + box-shadow: 0 4px 20px rgba(0,0,0,0.3); + min-width: 300px; +} +.modal h2 { + margin-top: 0; + color: #4CAF50; +} +.modal input { + width: 200px; + padding: 10px; + margin: 10px 0; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 16px; +} +.modal button { + background-color: #4CAF50; + color: white; + border: none; + padding: 10px 20px; + margin: 5px; + border-radius: 5px; + cursor: pointer; + font-size: 16px; +} +.modal button:hover { + background-color: #45a049; +} +.modal .cancel-btn { + background-color: #f44336; +} +.modal .cancel-btn:hover { + background-color: #da190b; +} + +/* Perbaikan tampilan parking-history di admin */ + +.back-btn { + background-color: #2196F3; + color: white; + border: none; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + font-size: 16px; + margin-bottom: 20px; +} +.back-btn:hover { + background-color: #0b7dda; +} + +/* Styling untuk indikator slot pada denah */ +.map-indicators { + position: relative; + display: inline-block; + margin-top: 10px; +} + +.slot-indicator { + position: absolute; + width: 15px; + height: 15px; + border-radius: 3px; + background-color: #4CAF50; + border: 2px solid #fff; + box-shadow: 0 2px 4px rgba(0,0,0,0.3); + display: flex; + align-items: center; + justify-content: center; + font-size: 8px; + font-weight: bold; + color: white; + text-shadow: 1px 1px 1px rgba(0,0,0,0.5); + transition: all 0.3s ease; +} + +.slot-indicator.occupied { + background-color: #f44336; +} + +.slot-indicator.available { + background-color: #4CAF50; +} + +/* Posisi indikator pada denah - sesuaikan dengan lokasi parkir Anda */ +#indicator_1 { + top: 39%; + right: 27%; +} + +#indicator_2 { + top: 51%; + right: 27%; +} + +#indicator_3 { + top: 63%; + right: 27%; +} + +/* Efek hover untuk indikator */ +.slot-indicator:hover { + transform: scale(1.2); + z-index: 10; +} + +/* Legend untuk indikator */ +.map-legend { + margin-top: 10px; + display: flex; + justify-content: center; + gap: 20px; + font-size: 12px; +} + +.legend-item { + display: flex; + align-items: center; + gap: 5px; +} + +.legend-color { + width: 12px; + height: 12px; + border-radius: 2px; + border: 1px solid #ccc; +} + +.legend-available { + background-color: #4CAF50; +} + +.legend-occupied { + background-color: #f44336; +} \ No newline at end of file