first commit

This commit is contained in:
Medicone1 2025-08-08 15:16:48 +07:00
commit fb3bcc6565
6 changed files with 1042 additions and 0 deletions

155
admin.html Normal file
View File

@ -0,0 +1,155 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dashboard Admin - Monitoring Parkir</title>
<link rel="stylesheet" href="style.css" />
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script type="module" src="script.js"></script>
<style>
.back-btn {
background-color: #4CAF50;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
margin-bottom: 25px;
transition: background-color 0.3s ease;
box-shadow: 0 4px 8px rgba(76, 175, 80, 0.4);
display: inline-block;
}
.back-btn:hover {
background-color: #388E3C;
box-shadow: 0 6px 12px rgba(56, 142, 60, 0.6);
transform: translateY(-2px);
}
.parking-history {
background-color: #fff;
border-radius: 10px;
padding: 25px 30px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
max-width: 480px;
margin: 0 auto;
display: flex;
flex-direction: column;
height: 450px;
}
.parking-history h3 {
margin-top: 0;
margin-bottom: 20px;
color: #4CAF50;
font-weight: 700;
font-size: 1.7em;
border-bottom: 3px solid #4CAF50;
padding-bottom: 8px;
text-align: center;
}
#history-container {
flex-grow: 1;
overflow-y: auto;
padding-right: 10px;
}
.history-list {
list-style: none;
padding: 0;
margin: 0;
}
.history-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 18px;
margin-bottom: 12px;
border-radius: 8px;
border-left: 6px solid;
background-color: #f9f9f9;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
transition: background-color 0.2s ease;
}
.history-item:hover {
background-color: #e6f7e6;
}
.history-item.entry {
border-left-color: #f44336;
}
.history-item.exit {
border-left-color: #4CAF50;
}
.history-info {
font-weight: 600;
font-size: 1.1em;
color: #333;
}
.history-info small {
display: block;
font-weight: normal;
color: #666;
font-size: 0.8em;
margin-top: 2px;
}
.history-time {
color: #666;
font-size: 0.9em;
font-style: italic;
white-space: nowrap;
}
.loading-message {
text-align: center;
color: #666;
font-style: italic;
padding: 20px;
}
/* Scrollbar custom */
#history-container::-webkit-scrollbar {
width: 8px;
}
#history-container::-webkit-scrollbar-track {
background: #eee;
border-radius: 10px;
}
#history-container::-webkit-scrollbar-thumb {
background-color: #4CAF50;
border-radius: 10px;
}
</style>
</head>
<body>
<div id="admin-view">
<div class="admin-header">
<h1>Dashboard Admin - Monitoring Parkir</h1>
</div>
<a href="index.html"><button class="back-btn">Kembali ke Mode Pengguna</button></a>
<div class="admin-content">
<div class="parking-status">
<h2>Status Parkir Saat Ini</h2>
<div id="sensor_1" class="sensor">
<img src="images/suv-car.png" alt="Logo Kendaraan">
<h3>Parkir Slot 1</h3>
<p id="status_1" class="status">Tersedia</p>
</div>
<div id="sensor_2" class="sensor">
<img src="images/suv-car.png" alt="Logo Kendaraan">
<h3>Parkir Slot 2</h3>
<p id="status_2" class="status">Tersedia</p>
</div>
<div id="sensor_3" class="sensor">
<img src="images/suv-car.png" alt="Logo Kendaraan">
<h3>Parkir Slot 3</h3>
<p id="status_3" class="status">Tersedia</p>
</div>
</div>
<div class="parking-history">
<h3>Riwayat Parkir Realtime</h3>
<div id="history-container">
<div class="loading-message">
🔄 Memuat riwayat parkir...
</div>
</div>
</div>
</div>
</div>
</body>
</html>

BIN
images/Denah2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
images/suv-car.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

74
index.html Normal file
View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Monitoring Slot Parkir</title>
<link rel="stylesheet" href="style.css" />
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script type="module" src="script.js"></script>
</head>
<body>
<div>
<button class="mode-btn" onclick="showAdminLogin()">Mode Developer/Admin</button>
</div>
<div id="user-view">
<h1>Monitoring Slot Parkir</h1>
<!-- Sensor Slot 1 -->
<div id="sensor_1" class="sensor">
<img src="images/suv-car.png" alt="Logo Kendaraan" />
<h3>Parkir Slot 1</h3>
<p class="status" id="status_1">Status: Tersedia</p>
</div>
<!-- Sensor Slot 2 -->
<div id="sensor_2" class="sensor">
<img src="images/suv-car.png" alt="Logo Kendaraan" />
<h3>Parkir Slot 2</h3>
<p class="status" id="status_2">Status: Tersedia</p>
</div>
<!-- Sensor Slot 3 -->
<div id="sensor_3" class="sensor">
<img src="images/suv-car.png" alt="Logo Kendaraan" />
<h3>Parkir Slot 3</h3>
<p class="status" id="status_3">Status: Tersedia</p>
</div>
<div class="map-container">
<h2>Denah Lokasi Parkir</h2>
<div class="map-indicators">
<img src="images/Denah2.png" alt="Denah Lokasi Parkir" style="max-width: 80%; height: auto;" />
<div id="indicator_1" class="slot-indicator available" title="Slot 1">1</div>
<div id="indicator_2" class="slot-indicator available" title="Slot 2">2</div>
<div id="indicator_3" class="slot-indicator available" title="Slot 3">3</div>
</div>
<div class="map-legend">
<div class="legend-item">
<div class="legend-color legend-available"></div>
<span>Tersedia</span>
</div>
<div class="legend-item">
<div class="legend-color legend-occupied"></div>
<span>Terisi</span>
</div>
</div>
<div class="direction">
<p style="font-size: 15px;">Notes: Tempat parkir berada di sebelah kanan, dekat dengan palang parkir.</p>
</div>
</div>
</div>
<!-- Admin Login Modal -->
<div id="admin-modal" class="modal">
<div class="modal-content">
<h2>Login Admin</h2>
<p>Masukkan password untuk mengakses mode developer:</p>
<input type="password" id="admin-password" placeholder="Password" onkeypress="handlePasswordKeypress(event)" />
<br />
<button onclick="loginAdmin()">Login</button>
<button class="cancel-btn" onclick="closeAdminModal()">Batal</button>
</div>
</div>
</body>
</html>

577
script.js Normal file
View File

@ -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 = '<div class="loading-message">📝 Belum ada riwayat parkir</div>';
return;
}
const historyArray = Object.values(data).sort((a, b) => new Date(b.date) - new Date(a.date));
let historyHTML = '<div class="history-list">';
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 += `
<div class="history-item ${statusClass}">
<div class="history-info">
<strong>Slot ${record.slot}</strong> - ${statusText}
<small>${sourceIcon} ${record.source} ${dataSourceIcon} ${record.dataSource || 'unknown'}</small>
</div>
<div class="history-time">${record.timestamp}</div>
</div>
`;
});
historyHTML += '</div>';
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");
}
});

236
style.css Normal file
View File

@ -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;
}