818 lines
41 KiB
PHP
818 lines
41 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Dashboard Admin</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
</head>
|
|
<body class="bg-gray-100 dark:bg-gray-900" x-data="{ isNotificationsPanelOpen: false }">
|
|
<!-- Navbar -->
|
|
<nav class="fixed top-0 left-0 right-0 z-30 bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
|
|
<div class="px-4 py-3 lg:px-6">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<img src="{{ asset('asset/logo.png') }}" alt="SmartCab Logo" class="h-8 w-8 lg:h-10 lg:w-10 mr-2">
|
|
<div class="flex flex-col">
|
|
<span class="text-base lg:text-xl font-semibold dark:text-white">SMARTCAB</span>
|
|
<span class="text-xs lg:text-sm font-medium dark:text-white">Smart Cabin Security & Monitoring</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Right Menu -->
|
|
<div class="flex items-center gap-2 lg:hidden">
|
|
<a href="{{route('ai.chat')}}" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">
|
|
AI
|
|
</a>
|
|
<!-- Notification Button Mobile -->
|
|
<button
|
|
@click="isNotificationsPanelOpen = true"
|
|
class="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white"
|
|
>
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Mobile Menu Button -->
|
|
<button id="mobileMenuBtn" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Desktop Menu -->
|
|
<div class="hidden lg:flex items-center gap-4">
|
|
<!-- Menu Items -->
|
|
<a href="{{route('ai.chat')}}" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">
|
|
AI
|
|
</a>
|
|
|
|
|
|
<!-- Notification Button -->
|
|
<button
|
|
@click="isNotificationsPanelOpen = true"
|
|
class="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white"
|
|
>
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Dark Mode Toggle -->
|
|
<button id="darkModeToggle" class="p-2 text-gray-500 dark:text-gray-400">
|
|
<svg class="w-6 h-6 dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
|
|
</svg>
|
|
<svg class="w-6 h-6 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Profile Dropdown -->
|
|
<div class="relative">
|
|
<button class="flex items-center text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">
|
|
<img class="w-8 h-8 rounded-full" src="{{ asset('asset/foto.jpg') }}" alt="profile">
|
|
<span class="ml-2 hidden lg:block"></span>
|
|
</button>
|
|
<div class="absolute right-0 hidden mt-2 w-48 bg-white rounded-md shadow-lg dark:bg-gray-700" id="profileMenu">
|
|
<div class="px-4 py-3 border-b dark:border-gray-600">
|
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-200">Hi!Vicky</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">vickynando12@gmail.com</p>
|
|
</div>
|
|
<a href="{{route('profile')}}" class="block px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-600">Your Profile</a>
|
|
<a href="{{route('logout')}}" class="block px-4 py-2 text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-600">Logout</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Menu -->
|
|
<div id="mobileMenu" class="hidden lg:hidden mt-4 space-y-4">
|
|
<!-- Profile Section Mobile -->
|
|
<div class="border-b pb-4 dark:border-gray-700">
|
|
<div class="flex items-center space-x-3 px-4">
|
|
<img class="w-10 h-10 rounded-full" src="{{ asset('asset/foto.jpg') }}" alt="profile">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-200">Hi!Vicky</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">vickynando12@gmail.com</p>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4 space-y-2">
|
|
<a href="{{route('profile')}}" class="block px-4 py-2 text-gray-600 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">Your Profile</a>
|
|
<a href="{{route('logout')}}" class="block px-4 py-2 text-gray-600 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">Logout</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col space-y-4 px-4">
|
|
<a href="#" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">Dashboard</a>
|
|
{{-- <a href="#" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">Chat</a> --}}
|
|
<!-- Dark Mode Toggle Mobile -->
|
|
<button id="mobileDarkModeToggle" class="flex items-center justify-between w-full text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-white">
|
|
<span>Dark Mode</span>
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Main Content -->
|
|
<main class="pt-20 p-4 dark:bg-gray-900">
|
|
<div class="max-w-7xl mx-auto transition-colors duration-200">
|
|
<!-- Info Cards -->
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 order-2 md:order-1">
|
|
<!-- Temperature -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 mr-4 text-red-500 bg-red-100 rounded-full dark:text-red-100 dark:bg-red-500">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Temperature</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" id="temp-value">{{ $temperature }}°C</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Humidity -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 mr-4 text-blue-500 bg-blue-100 rounded-full dark:text-blue-100 dark:bg-blue-500">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Humidity</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" id="humidity-value">{{ $humidity }}%</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Motion -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 mr-4 text-yellow-500 bg-yellow-100 rounded-full dark:text-yellow-100 dark:bg-yellow-500">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Motion</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" id="motion-value">{{ $motion }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Security Status -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
|
<div class="flex items-center">
|
|
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Security Status</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" id="status-value">{{ $status }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts and Door Status Section -->
|
|
<div class="mt-8 grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<!-- Door Lock Status Card -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
|
<!-- Door Status -->
|
|
<div class="flex items-center mb-6">
|
|
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Door Status</p>
|
|
<p class="text-lg font-semibold text-green-600 dark:text-green-400" id="servo-status">{{ $servo_status }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border-t dark:border-gray-700 pt-4">
|
|
<!-- Last Access & Device Status -->
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Last Access</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" id="last-access">{{ $last_access }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Device Status</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" id="device-status">{{ $status_device }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Temperature & Humidity Chart -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 transition-colors duration-200 order-2 md:order-1">
|
|
<h4 class="text-lg font-semibold text-gray-800 dark:text-gray-300 mb-4">Temperature & Humidity Overview</h4>
|
|
<div class="h-64">
|
|
<canvas id="sensorChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Notification Panel -->
|
|
<div
|
|
x-show="isNotificationsPanelOpen"
|
|
x-transition:enter="transform transition ease-in-out duration-300"
|
|
x-transition:enter-start="-translate-x-full"
|
|
x-transition:enter-end="translate-x-0"
|
|
x-transition:leave="transform transition ease-in-out duration-300"
|
|
x-transition:leave-start="translate-x-0"
|
|
x-transition:leave-end="-translate-x-full"
|
|
class="fixed inset-0 z-50"
|
|
>
|
|
<!-- Backdrop -->
|
|
<div
|
|
class="fixed inset-0 bg-black bg-opacity-50"
|
|
@click="isNotificationsPanelOpen = false"
|
|
></div>
|
|
|
|
<!-- Panel -->
|
|
<div
|
|
x-transition:enter="transform transition ease-in-out duration-300"
|
|
x-transition:enter-start="-translate-x-full"
|
|
x-transition:enter-end="translate-x-0"
|
|
x-transition:leave="transform transition ease-in-out duration-300"
|
|
x-transition:leave-start="translate-x-0"
|
|
x-transition:leave-end="-translate-x-full"
|
|
class="fixed inset-y-0 left-0 w-full max-w-xs bg-white dark:bg-gray-800 overflow-y-auto"
|
|
>
|
|
<div class="flex flex-col h-screen">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between p-4 border-b dark:border-primary-darker">
|
|
<h2 class="text-xl font-semibold text-gray-700 dark:text-light">Notifications</h2>
|
|
<button @click="isNotificationsPanelOpen = false" class="p-2 text-gray-600 rounded-md hover:bg-gray-100 dark:text-light dark:hover:bg-primary">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Notification Items -->
|
|
<div class="flex-1 p-4 space-y-4">
|
|
<!-- Sample Notification Item -->
|
|
<div class="flex p-4 bg-white dark:bg-darker rounded-lg shadow">
|
|
<div class="flex-shrink-0">
|
|
<span class="p-2 bg-blue-500 rounded-full text-white">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path>
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
<div class="ml-4">
|
|
<h4 class="text-sm font-semibold text-gray-700 dark:text-light">New Order Received</h4>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Order #123 has been placed</p>
|
|
<span class="text-xs text-gray-400 dark:text-gray-500">2 minutes ago</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ... more notification items -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Floating Action Button -->
|
|
<div class="fixed bottom-0 right-0 z-50 mb-5 mr-5">
|
|
<button class="flex h-14 w-14 items-center justify-center rounded-full bg-blue-500 text-white shadow-lg hover:bg-blue-600" onclick="toggleModal()">
|
|
<svg class="w-6 h-6 animate-spin" style="animation-duration: 3s;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Modal -->
|
|
<div id="controlModal" class="fixed inset-0 z-50 hidden">
|
|
<div class="fixed inset-0 bg-black bg-opacity-50" onclick="toggleModal()"></div>
|
|
<div class="fixed bottom-24 right-5 w-72 rounded-lg bg-white p-4 shadow-xl dark:bg-gray-800">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-900 dark:text-white">Security Controls</h3>
|
|
|
|
<!-- Security Toggle -->
|
|
<div class="flex items-center justify-between mb-4">
|
|
<span class="text-gray-700 dark:text-gray-200">Security System</span>
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
<input type="checkbox" id="securityToggle" class="sr-only peer" onchange="toggleSecurity(this)">
|
|
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Firebase SDK v8 -->
|
|
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
|
|
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js"></script>
|
|
|
|
<script>
|
|
// Firebase configuration
|
|
const firebaseConfig = {
|
|
apiKey: "AIzaSyCr8xNQIsPpIUMGIR9wEGmG7hgMDKf2H5I",
|
|
authDomain: "smartcab-8bb42.firebaseapp.com",
|
|
databaseURL: "https://smartcab-8bb42-default-rtdb.firebaseio.com",
|
|
projectId: "smartcab-8bb42",
|
|
storageBucket: "smartcab-8bb42.firebasestorage.app",
|
|
messagingSenderId: "539751617121",
|
|
appId: "1:539751617121:web:3a899309fdb5e29efa9020",
|
|
measurementId: "G-BQPQLLCJTR"
|
|
};
|
|
|
|
// Initialize Firebase
|
|
firebase.initializeApp(firebaseConfig);
|
|
const database = firebase.database();
|
|
|
|
let isUserAction = false;
|
|
|
|
// Function to update UI elements
|
|
function updateUIElement(elementId, value, suffix = '') {
|
|
const element = document.getElementById(elementId);
|
|
if (element) {
|
|
element.textContent = `${value}${suffix}`;
|
|
console.log(`Updated ${elementId} with value: ${value}${suffix}`);
|
|
} else {
|
|
console.error(`Element ${elementId} not found`);
|
|
}
|
|
}
|
|
|
|
// Function to update UI with new data
|
|
function updateUI(data) {
|
|
console.log('Updating UI with data:', data);
|
|
|
|
if (data.dht11) {
|
|
updateUIElement('temp-value', data.dht11.temperature, '°C');
|
|
updateUIElement('humidity-value', data.dht11.humidity, '%');
|
|
|
|
// Update chart if it exists
|
|
if (typeof sensorChart !== 'undefined') {
|
|
updateChart(Date.now(), data.dht11.temperature, data.dht11.humidity);
|
|
}
|
|
}
|
|
|
|
if (data.security) {
|
|
updateUIElement('motion-value', data.security.motion);
|
|
updateUIElement('status-value', data.security.status);
|
|
|
|
const toggle = document.getElementById('securityToggle');
|
|
if (toggle) {
|
|
toggle.checked = data.security.status === 'on';
|
|
}
|
|
}
|
|
|
|
// Add door status updates
|
|
updateDoorStatus(data);
|
|
}
|
|
|
|
// Function to fetch and update data
|
|
function fetchAndUpdateData() {
|
|
fetch('https://smartcab-8bb42-default-rtdb.firebaseio.com/.json')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
console.log('Data fetched successfully:', data);
|
|
updateUI(data);
|
|
})
|
|
.catch(error => {
|
|
console.error('Fetch error:', error);
|
|
});
|
|
}
|
|
|
|
// Function to toggle security status
|
|
function toggleSecurity(checkbox) {
|
|
isUserAction = true;
|
|
console.log('Toggle security called with state:', checkbox.checked);
|
|
|
|
const securityRef = database.ref('security/status');
|
|
const newStatus = checkbox.checked ? 'ON' : 'OFF';
|
|
|
|
checkbox.disabled = true;
|
|
|
|
securityRef.set(newStatus)
|
|
.then(() => {
|
|
console.log('Security status updated successfully to:', newStatus);
|
|
updateUIElement('status-value', newStatus);
|
|
})
|
|
.catch((error) => {
|
|
console.error('Error updating security status:', error);
|
|
checkbox.checked = !checkbox.checked;
|
|
alert('Failed to update security status. Please try again.');
|
|
})
|
|
.finally(() => {
|
|
checkbox.disabled = false;
|
|
setTimeout(() => {
|
|
isUserAction = false;
|
|
}, 1000);
|
|
});
|
|
}
|
|
|
|
// Listen for security status changes
|
|
database.ref('security/status').on('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
if (status && !isUserAction) {
|
|
console.log('Security status updated:', status);
|
|
updateUIElement('status-value', status);
|
|
|
|
const securityToggle = document.getElementById('securityToggle');
|
|
if (securityToggle && !securityToggle.disabled) {
|
|
securityToggle.checked = status === 'on';
|
|
}
|
|
}
|
|
});
|
|
|
|
// Initialize when document is ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('Document ready, initializing...');
|
|
fetchAndUpdateData();
|
|
setInterval(fetchAndUpdateData, 1000);
|
|
|
|
// Initialize charts
|
|
initializeCharts();
|
|
});
|
|
|
|
// Function to update chart data
|
|
function updateChart(timestamp, temperature, humidity) {
|
|
const MAX_DATA_POINTS = 5;
|
|
|
|
// Check if there's any change in the latest data
|
|
const lastTempData = sensorChart.data.datasets[0].data[sensorChart.data.datasets[0].data.length - 1];
|
|
const lastHumData = sensorChart.data.datasets[1].data[sensorChart.data.datasets[1].data.length - 1];
|
|
|
|
// Only update if data is different or we don't have enough data points
|
|
if (lastTempData !== temperature ||
|
|
lastHumData !== humidity ||
|
|
sensorChart.data.labels.length < MAX_DATA_POINTS) {
|
|
|
|
// Add new data
|
|
sensorChart.data.labels.push(new Date(timestamp).toLocaleTimeString());
|
|
sensorChart.data.datasets[0].data.push(temperature);
|
|
sensorChart.data.datasets[1].data.push(humidity);
|
|
|
|
// Remove old data if we have more than MAX_DATA_POINTS
|
|
if (sensorChart.data.labels.length > MAX_DATA_POINTS) {
|
|
sensorChart.data.labels.shift();
|
|
sensorChart.data.datasets[0].data.shift();
|
|
sensorChart.data.datasets[1].data.shift();
|
|
}
|
|
|
|
// Update chart only if there's a change
|
|
sensorChart.update();
|
|
console.log('Chart updated with new data - Temp:', temperature, 'Humidity:', humidity);
|
|
} else {
|
|
console.log('No change in sensor data, skipping chart update');
|
|
}
|
|
}
|
|
|
|
// Initialize the chart with empty data
|
|
const ctx = document.getElementById('sensorChart').getContext('2d');
|
|
const sensorChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: [],
|
|
datasets: [
|
|
{
|
|
label: 'Temperature',
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.5)',
|
|
data: [],
|
|
pointStyle: 'circle',
|
|
pointRadius: 6,
|
|
pointHoverRadius: 8,
|
|
tension: 0.4 // Menambahkan smoothing pada garis
|
|
},
|
|
{
|
|
label: 'Humidity',
|
|
borderColor: 'rgb(135, 206, 235)',
|
|
backgroundColor: 'rgba(135, 206, 235, 0.5)',
|
|
data: [],
|
|
pointStyle: 'circle',
|
|
pointRadius: 6,
|
|
pointHoverRadius: 8,
|
|
tension: 0.4 // Menambahkan smoothing pada garis
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
animation: {
|
|
duration: 750, // Durasi animasi dalam milidetik
|
|
easing: 'easeInOutQuart' // Tipe animasi
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
color: document.documentElement.classList.contains('dark') ? '#374151' : '#e5e7eb'
|
|
},
|
|
ticks: {
|
|
color: document.documentElement.classList.contains('dark') ? '#fff' : '#000'
|
|
}
|
|
},
|
|
x: {
|
|
grid: {
|
|
color: document.documentElement.classList.contains('dark') ? '#374151' : '#e5e7eb'
|
|
},
|
|
ticks: {
|
|
color: document.documentElement.classList.contains('dark') ? '#fff' : '#000'
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
color: document.documentElement.classList.contains('dark') ? '#fff' : '#000'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Mobile Menu Toggle
|
|
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
|
|
const mobileMenu = document.getElementById('mobileMenu');
|
|
|
|
mobileMenuBtn.addEventListener('click', () => {
|
|
mobileMenu.classList.toggle('hidden');
|
|
});
|
|
|
|
// Dark Mode Handler
|
|
const darkModeToggle = document.getElementById('darkModeToggle');
|
|
const mobileDarkModeToggle = document.getElementById('mobileDarkModeToggle');
|
|
|
|
function toggleDarkMode() {
|
|
const root = document.documentElement;
|
|
const isDark = root.classList.contains('dark');
|
|
|
|
if (isDark) {
|
|
root.classList.remove('dark');
|
|
localStorage.setItem('darkMode', 'disabled');
|
|
} else {
|
|
root.classList.add('dark');
|
|
localStorage.setItem('darkMode', 'enabled');
|
|
}
|
|
updateChartsTheme();
|
|
}
|
|
|
|
// Check system preference and localStorage
|
|
if (localStorage.getItem('darkMode') === 'enabled' ||
|
|
(localStorage.getItem('darkMode') === null &&
|
|
window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
document.documentElement.classList.add('dark');
|
|
}
|
|
|
|
darkModeToggle.addEventListener('click', toggleDarkMode);
|
|
mobileDarkModeToggle.addEventListener('click', toggleDarkMode);
|
|
|
|
// Update Tailwind dark mode classes
|
|
const observer = new MutationObserver((mutations) => {
|
|
mutations.forEach((mutation) => {
|
|
if (mutation.attributeName === 'class') {
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
document.body.className = isDark ? 'dark:bg-gray-900' : 'bg-gray-100';
|
|
|
|
// Update all dark mode elements
|
|
document.querySelectorAll('[class*="dark:"]').forEach(element => {
|
|
const darkClasses = Array.from(element.classList)
|
|
.filter(cls => cls.startsWith('dark:'));
|
|
|
|
darkClasses.forEach(cls => {
|
|
const lightClass = cls.replace('dark:', '');
|
|
if (isDark) {
|
|
element.classList.add(lightClass);
|
|
} else {
|
|
element.classList.remove(lightClass);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
observer.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ['class']
|
|
});
|
|
|
|
// Profile Menu
|
|
const profileButton = document.querySelector('button img');
|
|
const profileMenu = document.getElementById('profileMenu');
|
|
|
|
profileButton.addEventListener('click', () => {
|
|
profileMenu.classList.toggle('hidden');
|
|
});
|
|
|
|
document.addEventListener('click', (e) => {
|
|
if (!profileButton.contains(e.target) && !profileMenu.contains(e.target)) {
|
|
profileMenu.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
// Function to update charts theme
|
|
function updateChartsTheme() {
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
const textColor = isDark ? '#fff' : '#000';
|
|
const gridColor = isDark ? '#374151' : '#e5e7eb';
|
|
|
|
// Update both charts
|
|
[sensorChart, lineChart].forEach(chart => {
|
|
if (chart) {
|
|
chart.options.plugins.legend.labels.color = textColor;
|
|
if (chart.options.scales) {
|
|
Object.values(chart.options.scales).forEach(scale => {
|
|
scale.ticks.color = textColor;
|
|
scale.grid.color = gridColor;
|
|
});
|
|
}
|
|
chart.update();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Charts configuration
|
|
let lineChart;
|
|
|
|
// Initialize charts
|
|
function initializeCharts() {
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
const textColor = isDark ? '#fff' : '#000';
|
|
const gridColor = isDark ? '#374151' : '#e5e7eb';
|
|
|
|
// Line Chart
|
|
const lineCtx = document.getElementById('lineChart').getContext('2d');
|
|
lineChart = new Chart(lineCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
|
datasets: [{
|
|
label: 'Revenue',
|
|
data: [65, 59, 80, 81, 56, 55],
|
|
borderColor: '#3B82F6',
|
|
tension: 0.3
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
labels: {
|
|
color: textColor
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
ticks: {
|
|
color: textColor
|
|
},
|
|
grid: {
|
|
color: gridColor
|
|
}
|
|
},
|
|
x: {
|
|
ticks: {
|
|
color: textColor
|
|
},
|
|
grid: {
|
|
color: gridColor
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add these functions to your existing script
|
|
function toggleModal() {
|
|
const modal = document.getElementById('controlModal');
|
|
modal.classList.toggle('hidden');
|
|
|
|
// Update toggle state when modal opens
|
|
if (!modal.classList.contains('hidden')) {
|
|
database.ref('security/status').once('value', (snapshot) => {
|
|
const status = snapshot.val();
|
|
document.getElementById('securityToggle').checked = status === 'on';
|
|
});
|
|
}
|
|
}
|
|
|
|
// Add these functions for door lock functionality
|
|
let isDoorLocked = true; // Initial state
|
|
|
|
function toggleDoorLock() {
|
|
isDoorLocked = !isDoorLocked;
|
|
updateDoorStatus();
|
|
|
|
// Simulate last access update
|
|
const now = new Date();
|
|
document.getElementById('last-access').textContent = now.toLocaleTimeString();
|
|
|
|
// Here you would typically update Firebase
|
|
// Uncomment when ready to connect to Firebase
|
|
/*
|
|
const doorRef = database.ref('door');
|
|
doorRef.update({
|
|
status: isDoorLocked ? 'locked' : 'unlocked',
|
|
lastAccess: now.toISOString(),
|
|
deviceStatus: 'online'
|
|
}).catch(error => {
|
|
console.error('Error updating door status:', error);
|
|
isDoorLocked = !isDoorLocked; // Revert state if update fails
|
|
updateDoorStatus();
|
|
});
|
|
*/
|
|
}
|
|
|
|
function updateDoorStatus() {
|
|
const doorStatus = document.getElementById('servo-status');
|
|
if (doorStatus) {
|
|
doorStatus.textContent = isDoorLocked ? 'Locked' : 'Unlocked';
|
|
doorStatus.className = isDoorLocked
|
|
? 'text-lg font-semibold text-green-600 dark:text-green-400'
|
|
: 'text-lg font-semibold text-red-600 dark:text-red-400';
|
|
}
|
|
}
|
|
|
|
// Add to your document ready function
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// ... existing initialization code ...
|
|
|
|
// Initialize door status
|
|
updateDoorStatus();
|
|
|
|
// Update device status periodically
|
|
setInterval(() => {
|
|
const deviceStatus = document.getElementById('device-status');
|
|
if (deviceStatus) {
|
|
// Here you would typically check actual device connection
|
|
deviceStatus.textContent = 'Online';
|
|
deviceStatus.className = 'text-lg font-semibold text-green-600 dark:text-green-400';
|
|
}
|
|
}, 5000);
|
|
});
|
|
|
|
// Function to update door status UI
|
|
function updateDoorStatus(data) {
|
|
if (data.smartcab) {
|
|
// Update servo status
|
|
const servoStatus = document.getElementById('servo-status');
|
|
if (servoStatus) {
|
|
servoStatus.textContent = data.smartcab.servo_status;
|
|
// Update color based on status
|
|
if (data.smartcab.servo_status === 'terkunci') {
|
|
servoStatus.className = 'text-lg font-semibold text-green-600 dark:text-green-400';
|
|
} else if (data.smartcab.servo_status === 'terbuka') {
|
|
servoStatus.className = 'text-lg font-semibold text-red-600 dark:text-red-400';
|
|
}
|
|
}
|
|
|
|
// Update last access
|
|
const lastAccess = document.getElementById('last-access');
|
|
if (lastAccess) {
|
|
lastAccess.textContent = data.smartcab.last_access;
|
|
}
|
|
|
|
// Update device status
|
|
const deviceStatus = document.getElementById('device-status');
|
|
if (deviceStatus) {
|
|
deviceStatus.textContent = data.smartcab.status_device;
|
|
// Update color based on status
|
|
if (data.smartcab.status_device === 'Online') {
|
|
deviceStatus.className = 'text-lg font-semibold text-green-600 dark:text-green-400';
|
|
} else {
|
|
deviceStatus.className = 'text-lg font-semibold text-green-600 dark:text-green-400';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Listen for smartcab changes
|
|
database.ref('smartcab').on('value', (snapshot) => {
|
|
const data = snapshot.val();
|
|
if (data) {
|
|
updateDoorStatus(data);
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|