TKK_E32230273/header.php

1462 lines
56 KiB
PHP

<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
if (!isset($pdo)) {
include_once 'koneksi.php';
}
$stmt_check_user = $pdo->prepare("SELECT id_account FROM account WHERE id_account = ?");
$stmt_check_user->execute([$_SESSION['user_id']]);
if (!$stmt_check_user->fetch()) {
session_destroy();
header("Location: login.php");
exit;
}
$id_account = isset($_SESSION['id_account']) ? (int) $_SESSION['id_account'] : (isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 1);
try {
$sidebarHistoryCount = $pdo->query("SELECT COUNT(*) FROM history WHERE is_read = 0 AND is_deleted = 0")->fetchColumn();
} catch (Exception $e) {
$sidebarHistoryCount = 0;
}
try {
$sidebarNotifCount = $pdo->query("SELECT COUNT(*) FROM notifications WHERE is_deleted = 0 AND is_read = 0")->fetchColumn();
} catch (Exception $e) {
$sidebarNotifCount = 0;
}
try {
$stmtChatCount = $pdo->prepare("
SELECT COUNT(c.id_chat)
FROM chats c
INNER JOIN occupant o ON c.id_occupant = o.id_occupant
WHERE c.id_account = ? AND c.sender = 'occupant' AND c.is_read = 0
");
$stmtChatCount->execute([$id_account]);
$sidebarChatCount = $stmtChatCount->fetchColumn();
} catch (Exception $e) {
$sidebarChatCount = 0;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Identia Admin</title>
<!-- plugins:css -->
<link rel="stylesheet" href="vendors/feather/feather.css">
<link rel="stylesheet" href="vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" href="vendors/css/vendor.bundle.base.css">
<!-- endinject -->
<!-- Plugin css for this page -->
<link rel="stylesheet" href="vendors/datatables.net-bs4/dataTables.bootstrap4.css">
<link rel="stylesheet" href="vendors/ti-icons/css/themify-icons.css">
<link rel="stylesheet" type="text/css" href="js/select.dataTables.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
<!-- End plugin css for this page -->
<!-- inject:css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/vertical-layout-light/style.css">
<!-- endinject -->
<link rel="shortcut icon" href="images/myn.png" />
<style>
/* Override template logo constraints so user's width/height attributes work */
.navbar .navbar-brand-wrapper .navbar-brand img {
height: auto !important;
max-height: 100% !important;
}
</style>
<!-- Web Push Notifications Logic -->
<script>
const VAPID_PUBLIC_KEY = 'BNKv1xfnk1VVnKdwRZeu7n0QvZReL3Of7-_Hi1KvicWuDpcDrGNU2srydyOX9sUrvyzQ_L9UgYlbPy7X8ZAHINA';
function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
async function subscribeToPush() {
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
alert('Perhatian: Fitur Notifikasi Web Push TIDAK MENDUKUNG akses HTTP biasa. Pastikan situs Anda diakses menggunakan HTTPS (Secure) atau fitur ini tidak akan bekerja.');
console.warn('Push messaging is not supported by this browser.');
return;
}
try {
await navigator.serviceWorker.register('/sw.js');
const swReady = await navigator.serviceWorker.ready;
const subscription = await swReady.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlB64ToUint8Array(VAPID_PUBLIC_KEY)
});
// Send subscription to server
const response = await fetch('/save_subscription.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ subscription: subscription })
});
const result = await response.json();
if (result.status !== 'success') {
alert('Failed to save push subscription to DB: ' + result.message);
} else {
console.log('Push subscription saved.');
}
} catch (error) {
alert('Browser Push Error: ' + error.message);
console.error('Failed to subscribe the user: ', error);
}
}
// Automatically ask for permission if not granted
document.addEventListener('DOMContentLoaded', () => {
if ('Notification' in window) {
if (Notification.permission !== 'granted') {
Notification.requestPermission().then(permission => {
if (permission === 'granted') subscribeToPush();
});
} else {
subscribeToPush();
}
}
});
// Mobile specific trick: prompt on first meaningful user interaction (Avatar or Logo tap)
function initPushOnTouch() {
if ('Notification' in window && Notification.permission !== 'granted') {
Notification.requestPermission().then(permission => {
if (permission === 'granted') subscribeToPush();
});
}
// Remove listeners after first trigger
const profileBtn = document.getElementById('profileDropdown');
const logoBtn1 = document.querySelector('.brand-logo');
const logoBtn2 = document.querySelector('.brand-logo-mini');
if (profileBtn) profileBtn.removeEventListener('click', initPushOnTouch);
if (logoBtn1) logoBtn1.removeEventListener('click', initPushOnTouch);
if (logoBtn2) logoBtn2.removeEventListener('click', initPushOnTouch);
}
document.addEventListener('DOMContentLoaded', () => {
// Attach to Profile dropdown
const profileBtn = document.getElementById('profileDropdown');
if (profileBtn) profileBtn.addEventListener('click', initPushOnTouch, { once: true });
// Attach to Logos
const logoBtn1 = document.querySelector('.brand-logo');
if (logoBtn1) logoBtn1.addEventListener('click', initPushOnTouch, { once: true });
const logoBtn2 = document.querySelector('.brand-logo-mini');
if (logoBtn2) logoBtn2.addEventListener('click', initPushOnTouch, { once: true });
});
</script>
</head>
<body>
<div class="container-scroller">
<!-- partial:partials/_navbar.html -->
<nav class="navbar col-lg-12 col-12 p-0 fixed-top d-flex flex-row">
<div class="text-center navbar-brand-wrapper d-flex align-items-center justify-content-center">
<a class="navbar-brand brand-logo mr-5" href="dashboard.php"><img src="images/ko.png" class="mr-2" alt="logo"
width="100" height="110" /></a>
<a class="navbar-brand brand-logo-mini" href="dashboard.php"><img src="images/myn.png" alt="logo" /></a>
</div>
<div class="navbar-menu-wrapper d-flex align-items-center justify-content-end">
<button class="navbar-toggler navbar-toggler align-self-center" type="button" data-toggle="minimize">
<span class="icon-menu"></span>
</button>
<ul class="navbar-nav mr-lg-2">
<li class="nav-item nav-search d-none d-lg-block">
<div class="input-group" style="position: relative;">
<div class="input-group-prepend hover-cursor" id="navbar-search-icon">
<span class="input-group-text" id="search">
<i class="icon-search"></i>
</span>
</div>
<input type="text" class="form-control" id="navbar-search-input" placeholder="Search now"
aria-label="search" aria-describedby="search" autocomplete="off">
<!-- Search Results Dropdown -->
<div class="search-dropdown" id="searchDropdown" style="display: none;">
<div class="search-tabs" id="searchTabs">
<button class="search-tab-item active" data-tab="all">All</button>
<button class="search-tab-item" data-tab="overview">Overview</button>
<button class="search-tab-item" data-tab="account">Account</button>
<button class="search-tab-item" data-tab="occupant">Occupant</button>
</div>
<div class="search-results" id="searchResults">
<div class="search-empty">Type to search...</div>
</div>
</div>
</div>
</li>
</ul>
<ul class="navbar-nav navbar-nav-right">
<li class="nav-item nav-profile dropdown">
<a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" id="profileDropdown">
<?php
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$headerFotoSrc = 'images/admin.png';
?>
<span
style="font-family:'Inter','Segoe UI',sans-serif;font-size:13px;font-weight:500;color:#212529;margin-right:8px;display:inline-flex;align-items:center;"><?= htmlspecialchars(isset($_SESSION['name']) ? $_SESSION['name'] : '') ?><i
class="mdi mdi-check-decagram" style="color: #1da1f2; font-size: 16px; margin-left: 4px;"
title="Verified"></i></span>
<img src="<?= htmlspecialchars($headerFotoSrc) ?>" alt="profile" style="object-fit: cover;" />
</a>
<div class="dropdown-menu dropdown-menu-right navbar-dropdown" aria-labelledby="profileDropdown">
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal">
<i class="ti-power-off text-primary"></i>
Logout
</a>
</div>
</li>
<li class="nav-item nav-settings d-none d-lg-flex">
<a class="nav-link" href="#" style="background: transparent !important; padding: 0 15px; border: none;">
<i class="icon-ellipsis" style="font-size: 20px; color: #6c7383;"></i>
</a>
</li>
</ul>
<button class="navbar-toggler navbar-toggler-right d-lg-none align-self-center" type="button"
data-toggle="offcanvas">
<span class="icon-menu"></span>
</button>
</div>
</nav>
<!-- partial -->
<div class="container-fluid page-body-wrapper">
<!-- partial:partials/_settings-panel.html -->
<div id="right-sidebar" class="settings-panel"
style="background-color: #ffffff; border-left: 1px solid #ebedf2; box-shadow: none;">
<i class="settings-close ti-close"
style="position: absolute; right: 20px; top: 16px; font-size: 16px; cursor: pointer; color: #6c7383; z-index: 2;"></i>
<div class="d-flex align-items-center justify-content-between"
style="padding: 15px 50px 15px 20px; border-bottom: 1px solid #ebedf2; position: relative;">
<p class="mb-0" style="font-size:15px; font-weight:bold; color:#5856d6; letter-spacing: 0.5px;">CHATS</p>
<a href="chats.php" style="text-decoration:none;"><small class="font-weight-bold" style="color: #6c7383;">See
All</small></a>
</div>
<div class="tab-content" id="setting-content" style="padding: 0; overflow-y: auto; height: calc(100vh - 65px);">
<div class="chat-list-wrapper" id="headerChatsList" style="padding-top: 5px;">
<div style="text-align:center; padding:20px; color:#999; font-size:13px;">Loading...</div>
</div>
</div>
</div>
<!-- partial -->
<!-- partial:partials/_sidebar.html -->
<nav class="sidebar sidebar-offcanvas" id="sidebar">
<ul class="nav">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">
<i class="icon-grid menu-icon"></i>
<span class="menu-title">Dashboard</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="collapse" href="#occupant-menu" aria-expanded="false"
aria-controls="occupant-menu">
<i class="icon-layout menu-icon"></i>
<span class="menu-title">Occupant</span>
<i class="menu-arrow"></i>
</a>
<div class="collapse" id="occupant-menu">
<ul class="nav flex-column sub-menu">
<li class="nav-item"> <a class="nav-link" href="new_occupant.php"> New </a></li>
<li class="nav-item"> <a class="nav-link" href="manage_occupant.php"> Manage </a></li>
</ul>
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="access.php">
<i class="icon-toggle menu-icon"></i>
<span class="menu-title">Access</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="room.php">
<i class="ti-home menu-icon"></i>
<span class="menu-title">Room</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="account.php">
<i class="icon-head menu-icon"></i>
<span class="menu-title">Account</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="history.php">
<i class="icon-paper menu-icon"></i>
<span class="menu-title">History</span>
<?php if ($sidebarHistoryCount > 0): ?>
<span class="badge ml-auto" id="sidebar-history-badge"
style="background:#e5e7eb; color:#374151; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 20px;"><?= $sidebarHistoryCount ?></span>
<?php else: ?>
<span class="badge ml-auto" id="sidebar-history-badge"
style="background:#e5e7eb; color:#374151; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 20px; display: none;">0</span>
<?php endif; ?>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="schedule.php">
<i class="icon-clock menu-icon"></i>
<span class="menu-title">Schedule</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="notifications.php">
<div style="position:relative; display:flex; align-items:center;">
<i class="icon-bell menu-icon" style="margin-right: 0;"></i>
<?php if ($sidebarNotifCount > 0): ?>
<span class="badge" id="sidebar-notif-badge"
style="position: absolute; top: -2px; right: -2px; width: 6px; height: 6px; background: #ef4444; border-radius: 50%; padding: 0; display: inline-block;"></span>
<?php else: ?>
<span class="badge" id="sidebar-notif-badge"
style="position: absolute; top: -2px; right: -2px; width: 6px; height: 6px; background: #ef4444; border-radius: 50%; padding: 0; display: none;"></span>
<?php endif; ?>
</div>
<span class="menu-title" style="margin-left: 14px;">Notifications</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="chats.php">
<i class="ti-comments menu-icon"></i>
<span class="menu-title">Chat</span>
<?php if ($sidebarChatCount > 0): ?>
<span class="badge ml-auto" id="sidebar-chat-badge"
style="background:#0284c7; color:#fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 20px;"><?= $sidebarChatCount ?></span>
<?php else: ?>
<span class="badge ml-auto" id="sidebar-chat-badge"
style="background:#0284c7; color:#fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 20px; display: none;">0</span>
<?php endif; ?>
</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link" href="design.php">
<i class="ti-palette menu-icon"></i>
<span class="menu-title">Design</span>
</a>
</li> -->
<!-- <li class="nav-item">
<a class="nav-link" data-toggle="collapse" href="#form-elements" aria-expanded="false" aria-controls="form-elements">
<i class="icon-columns menu-icon"></i>
<span class="menu-title">Form elements</span>
<i class="menu-arrow"></i>
</a>
<div class="collapse" id="form-elements">
<ul class="nav flex-column sub-menu">
<li class="nav-item"><a class="nav-link" href="pages/forms/basic_elements.html">Basic Elements</a></li>
</ul>
</div>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="collapse" href="#charts" aria-expanded="false" aria-controls="charts">
<i class="icon-bar-graph menu-icon"></i>
<span class="menu-title">Charts</span>
<i class="menu-arrow"></i>
</a>
<div class="collapse" id="charts">
<ul class="nav flex-column sub-menu">
<li class="nav-item"> <a class="nav-link" href="pages/charts/chartjs.html">ChartJs</a></li>
</ul>
</div>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="collapse" href="#tables" aria-expanded="false" aria-controls="tables">
<i class="icon-grid-2 menu-icon"></i>
<span class="menu-title">Tables</span>
<i class="menu-arrow"></i>
</a>
<div class="collapse" id="tables">
<ul class="nav flex-column sub-menu">
<li class="nav-item"> <a class="nav-link" href="pages/tables/basic-table.html">Basic table</a></li>
</ul>
</div>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="collapse" href="#icons" aria-expanded="false" aria-controls="icons">
<i class="icon-contract menu-icon"></i>
<span class="menu-title">Icons</span>
<i class="menu-arrow"></i>
</a>
<div class="collapse" id="icons">
<ul class="nav flex-column sub-menu">
<li class="nav-item"> <a class="nav-link" href="pages/icons/mdi.html">Mdi icons</a></li>
</ul>
</div>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="collapse" href="#auth" aria-expanded="false" aria-controls="auth">
<i class="icon-head menu-icon"></i>
<span class="menu-title">User Pages</span>
<i class="menu-arrow"></i>
</a>
<div class="collapse" id="auth">
<ul class="nav flex-column sub-menu">
<li class="nav-item"> <a class="nav-link" href="pages/samples/login.html"> Login </a></li>
<li class="nav-item"> <a class="nav-link" href="pages/samples/register.html"> Register </a></li>
</ul>
</div>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="collapse" href="#error" aria-expanded="false" aria-controls="error">
<i class="icon-ban menu-icon"></i>
<span class="menu-title">Error pages</span>
<i class="menu-arrow"></i>
</a>
<div class="collapse" id="error">
<ul class="nav flex-column sub-menu">
<li class="nav-item"> <a class="nav-link" href="pages/samples/error-404.html"> 404 </a></li>
<li class="nav-item"> <a class="nav-link" href="pages/samples/error
-500.html"> 500 </a></li>
</ul>
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="pages/documentation/documentation.html">
<i class="icon-paper menu-icon"></i>
<span class="menu-title">Documentation</span>
</a>
</li> -->
</ul>
</nav>
<!-- Logout Modal Custom & Creative -->
<div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document" style="max-width: 400px;">
<div class="modal-content border-0 custom-modal-logout">
<div class="modal-body text-center p-5">
<div class="modal-icon-container mb-4 mx-auto">
<div class="icon-circle">
<i class="ti-power-off"></i>
</div>
</div>
<h4 class="modal-title-custom mb-2">Are You Sure You Want to Log Out?</h4>
<p class="text-muted mb-4 modal-desc-custom">Your session will end shortly. Make sure your work has been
saved safely.</p>
<div class="d-flex justify-content-center align-items-center mt-4 pt-2">
<button type="button" class="btn btn-cancel-custom mr-3" data-dismiss="modal">
<i class="ti-close mr-1 text-muted"></i> Cancel
</button>
<a href="login.php" class="btn btn-logout-custom">
<i class="ti-check mr-1 text-white"></i> Yes, Log Out
</a>
</div>
</div>
</div>
</div>
</div>
<style>
/* ========================================================= */
/* CUSTOM LOGOUT MODAL STYLING */
/* ========================================================= */
.custom-modal-logout {
border-radius: 10px !important;
box-shadow: none !important;
overflow: hidden;
background-color: #ffffff;
}
.custom-modal-logout .modal-body {
position: relative;
z-index: 1;
}
/* Hiasan background abstrak */
.custom-modal-logout::before {
content: "";
position: absolute;
top: -60px;
left: -40px;
width: 180px;
height: 180px;
background: rgba(255, 255, 255, 0.04);
border-radius: 50%;
z-index: 0;
}
.custom-modal-logout::after {
content: "";
position: absolute;
bottom: -50px;
right: -50px;
width: 140px;
height: 140px;
background: rgba(255, 71, 71, 0.04);
border-radius: 50%;
z-index: 0;
}
.modal-icon-container {
position: relative;
width: 85px;
height: 85px;
display: flex;
justify-content: center;
align-items: center;
z-index: 2;
}
.modal-icon-container .icon-circle {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #ff0000 0%, #ff4d4d 100%);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 8px 25px rgba(255, 0, 0, 0.5);
animation: pulse-soft 2s infinite;
}
.modal-icon-container i {
font-size: 36px;
color: #ffffff;
font-weight: bold;
}
.modal-title-custom {
font-weight: 700;
color: #2c2e33;
font-size: 1.4rem;
z-index: 2;
position: relative;
}
.modal-desc-custom {
font-size: 0.95rem;
line-height: 1.6;
z-index: 2;
position: relative;
padding: 0 10px;
}
.btn-cancel-custom {
border-radius: 10px !important;
font-weight: 600;
padding: 10px 24px;
color: #6c7383;
background-color: #f4f5f7;
border: 1px solid #e1e3e9;
transition: all 0.3s ease;
z-index: 2;
position: relative;
display: inline-flex;
align-items: center;
}
.btn-cancel-custom:hover {
background-color: #e4e6ea;
transform: translateY(-2px);
}
.btn-logout-custom {
border-radius: 10px !important;
font-weight: 600;
padding: 10px 26px;
background: linear-gradient(135deg, #4B49AC 0%, #4B49AC 100%);
border: none;
transition: all 0.3s ease;
z-index: 2;
position: relative;
color: #ffffff !important;
display: inline-flex;
align-items: center;
}
.btn-logout-custom:hover {
background: linear-gradient(135deg, #4B49AC 0%, #4B49AC 100%);
transform: translateY(-2px);
color: #ffffff !important;
}
@keyframes pulse-soft {
0% {
box-shadow: 0 0 0 0 rgba(255, 107, 107, 0.5);
}
70% {
box-shadow: 0 0 0 20px rgba(255, 107, 107, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(255, 107, 107, 0);
}
}
/* Mengubah border radius semua button dengan class .btn */
.btn {
border-radius: 4px !important;
}
.btn-primary {
border-radius: 6px !important;
}
.navbar {
-webkit-box-shadow: none !important;
-moz-box-shadow: none !important;
box-shadow: none !important;
border-bottom: 1px solid #ebedf2;
}
.sidebar {
border-right: 1px solid #ebedf2;
}
.sidebar .nav {
padding-top: 5px !important;
margin-top: 5px !important;
}
.sidebar .nav>.nav-item:first-child {
margin-top: 5px !important;
padding-top: 5px !important;
}
.sidebar .nav .nav-item .nav-link {
color: #262626 !important;
display: flex !important;
align-items: center !important;
}
.sidebar .nav .nav-item .nav-link i.menu-icon {
color: #262626 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
width: 20px !important;
min-width: 20px !important;
}
.sidebar .nav .nav-item .nav-link .menu-title {
color: #262626 !important;
line-height: 1 !important;
}
.sidebar .nav .nav-item .nav-link i.menu-arrow {
color: #262626 !important;
display: flex !important;
align-items: center !important;
}
/* Hover Teks (Seperti Website) */
.sidebar .nav:not(.sub-menu)>.nav-item:hover>.nav-link,
.sidebar .nav:not(.sub-menu)>.nav-item:hover>.nav-link .menu-title,
.sidebar .nav:not(.sub-menu)>.nav-item:hover>.nav-link i.menu-icon,
.sidebar .nav:not(.sub-menu)>.nav-item:hover>.nav-link i.menu-arrow {
color: #000000 !important;
}
/* ========================================================= */
/* OVERRIDE TAMPILAN MENU AKTIF Identia */
/* ========================================================= */
/* 1. Hilangkan latar belakang biru dari seluruh blok <li> utama dalam semua state (apapun yang terjadi) */
.sidebar .nav .nav-item,
.sidebar .nav .nav-item.active,
.sidebar .nav .nav-item:hover,
.sidebar .nav .nav-item.hover-open {
background: transparent !important;
}
/* Untuk Identia dark/light theme khusus */
.sidebar-light-theme .sidebar .nav .nav-item,
.sidebar-light-theme .sidebar .nav .nav-item.active {
background: transparent !important;
}
/* Hover Background (Seperti Website) */
.sidebar .nav:not(.sub-menu)>.nav-item:hover>.nav-link {
background: #e0e3e8 !important;
border-radius: 8px !important;
}
/* 2. Active Background (Seperti Home) */
.sidebar .nav .nav-item.active>.nav-link,
.sidebar .nav .nav-item.active>.nav-link[aria-expanded="true"],
.sidebar .nav .nav-item>.nav-link[aria-expanded="true"],
.sidebar-light-theme .sidebar .nav .nav-item.active>.nav-link,
.sidebar-light-theme .sidebar .nav .nav-item.active>.nav-link[aria-expanded="true"] {
background: #eceff3 !important;
margin-bottom: 0 !important;
border-radius: 8px !important;
overflow: hidden !important;
box-shadow: none !important;
border: none !important;
clip-path: inset(0px round 8px) !important;
}
/* ========================================================= */
/* FIX: Sidebar minimized (icon-only) mode */
/* ========================================================= */
.sidebar-icon-only .sidebar .nav .nav-item .nav-link {
display: block !important;
text-align: center !important;
padding-left: .5rem !important;
padding-right: .5rem !important;
position: static !important;
}
.sidebar-icon-only .sidebar .nav .nav-item .nav-link i.menu-icon {
display: block !important;
margin: 0 auto !important;
width: auto !important;
min-width: auto !important;
font-size: 18px !important;
}
.sidebar-icon-only .sidebar .nav .nav-item .nav-link .menu-title,
.sidebar-icon-only .sidebar .nav .nav-item .nav-link .badge,
.sidebar-icon-only .sidebar .nav .nav-item .nav-link .menu-sub-title {
display: none !important;
}
.sidebar-icon-only .sidebar .nav .nav-item .nav-link i.menu-arrow {
display: none !important;
}
/* Active state: full width, no radius */
.sidebar-icon-only .sidebar .nav .nav-item.active>.nav-link,
.sidebar-icon-only .sidebar .nav .nav-item.active>.nav-link[aria-expanded="true"],
.sidebar-icon-only .sidebar .nav .nav-item>.nav-link[aria-expanded="true"] {
border-radius: 0 !important;
clip-path: none !important;
}
/* Hover: subtle background, no white text on transparent */
.sidebar-icon-only .sidebar .nav:not(.sub-menu)>.nav-item:hover>.nav-link {
background: #e0e3e8 !important;
border-radius: 0 !important;
clip-path: none !important;
}
.sidebar-icon-only .sidebar .nav:not(.sub-menu)>.nav-item:hover>.nav-link i.menu-icon {
color: #000000 !important;
}
/* Hover-open menu title popup */
.sidebar-icon-only .sidebar .nav .nav-item.hover-open .nav-link .menu-title {
display: flex !important;
align-items: center !important;
background: #e0e3e8 !important;
color: #000000 !important;
padding: 0.5rem 1.4rem !important;
left: 70px !important;
position: absolute !important;
text-align: left !important;
top: 0 !important;
bottom: 0 !important;
width: 190px !important;
z-index: 1 !important;
line-height: 1.8 !important;
border-radius: 0 5px 5px 0 !important;
box-shadow: 4px 0px 7px 0px rgba(0, 0, 0, 0.08) !important;
}
/* Hover-open submenu popup */
.sidebar-icon-only .sidebar .nav .nav-item.hover-open .collapse,
.sidebar-icon-only .sidebar .nav .nav-item.hover-open .collapsing {
display: block !important;
background: #fff !important;
border-radius: 0 0 5px 0 !important;
position: absolute !important;
left: 70px !important;
width: 190px !important;
box-shadow: 4px 4px 7px 0px rgba(0, 0, 0, 0.08) !important;
}
/* Active Teks (Seperti Home) */
.sidebar .nav .nav-item.active>.nav-link,
.sidebar .nav .nav-item.active>.nav-link .menu-title,
.sidebar .nav .nav-item.active>.nav-link i.menu-icon,
.sidebar .nav .nav-item.active>.nav-link i.menu-arrow,
.sidebar .nav .nav-item>.nav-link[aria-expanded="true"] .menu-title,
.sidebar .nav .nav-item>.nav-link[aria-expanded="true"] i.menu-icon,
.sidebar .nav .nav-item>.nav-link[aria-expanded="true"] i.menu-arrow {
color: #000000 !important;
}
/* 4. Reset background Container Sub-menu agar SELALU transparan di kondisi APAPUN */
.sidebar .nav .nav-item .collapse,
.sidebar .nav .nav-item .collapsing,
.sidebar .nav .nav-item .sub-menu {
background: transparent !important;
}
/* 5. Setel Teks Sub-menu kembali ke abu-abu gelap normal di semua kondisi */
.sidebar .nav .nav-item .sub-menu .nav-item .nav-link,
.sidebar .nav .nav-item .collapse .sub-menu .nav-item .nav-link {
color: #6c7383 !important;
background: transparent !important;
font-weight: 400 !important;
}
/* Matikan gaya titik peluru list bawaan HTML */
.sidebar .nav .sub-menu {
list-style: none !important;
}
.sidebar .nav .sub-menu .nav-item {
list-style-type: none !important;
}
.sidebar .nav .sub-menu .nav-item::before {
display: none !important;
content: "" !important;
}
/* 6. Matikan pewarnaan khusus peluru (.) bawaan .active agar kembali normal */
.sidebar .nav .nav-item.active .sub-menu .nav-item .nav-link::before,
.sidebar .nav .nav-item.active .collapse .sub-menu .nav-item .nav-link::before {
content: "" !important;
/* Hapus content kotak/icon jelek */
color: #a3a4a5 !important;
}
/* Restorasi bentuk bulat bawaan Identia via ::before di tag \<a> */
.sidebar .nav .sub-menu .nav-item .nav-link::before {
content: "" !important;
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #a3a4a5;
margin-right: 10px;
flex-shrink: 0;
}
/* Menggeser (menarik) letak kumpulan submenu agak lebih ke kiri jika dibutuhkan */
.sidebar .nav .sub-menu {
padding-left: 1.5rem !important;
padding-top: 2px !important;
padding-bottom: 2px !important;
margin-top: -1px !important;
margin-bottom: -1px !important;
}
.sidebar .nav .sub-menu .nav-item .nav-link {
padding-top: 10px !important;
padding-bottom: 10px !important;
}
.sidebar .nav .nav-item .collapse,
.sidebar .nav .nav-item .collapsing {
margin-top: 0 !important;
margin-bottom: 0 !important;
}
/* 7. SAAT SUBMENU AKTIF ATAU DIKLIK (warna teks + titik membulat menjadi hitam gelap) */
.sidebar .nav .sub-menu .nav-item .nav-link.active,
.sidebar .nav .sub-menu .nav-item .nav-link:hover,
.sidebar .nav .nav-item.active .sub-menu .nav-item .nav-link.active,
.sidebar .nav .nav-item.active .sub-menu .nav-item .nav-link:hover {
color: #212025 !important;
font-weight: 500 !important;
}
/* Titik bulat berubah hitam gelap jika sub-menu tersebut aktif */
.sidebar .nav .sub-menu .nav-item .nav-link.active::before,
.sidebar .nav .sub-menu .nav-item .nav-link:hover::before,
.sidebar .nav .nav-item.active .sub-menu .nav-item .nav-link.active::before,
.sidebar .nav .nav-item.active .sub-menu .nav-item .nav-link:hover::before {
background-color: #212025 !important;
}
/* ========================================================= */
/* CHAT SIDEBAR MENU STYLING */
/* ========================================================= */
.contact-item {
display: flex;
padding: 12px 20px;
gap: 12px;
cursor: pointer;
transition: 0.2s;
align-items: center;
text-decoration: none;
border-bottom: 1px solid #f0f0f0;
}
.contact-item:hover {
background: #f8faff;
text-decoration: none;
}
.contact-item .ci-avatar {
width: 42px;
height: 42px;
border-radius: 50%;
position: relative;
flex-shrink: 0;
}
.contact-item .ci-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.contact-item .ci-status {
position: absolute;
bottom: 2px;
right: 2px;
width: 10px;
height: 10px;
border-radius: 50%;
background: #10b981;
border: 2px solid #fff;
}
.contact-item .ci-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.contact-item .ci-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 4px;
}
.contact-item .ci-name {
font-size: 14px;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.contact-item .ci-time {
font-size: 11px;
color: #999;
flex-shrink: 0;
}
.contact-item .ci-msg {
font-size: 12px;
color: #666;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.2;
}
.contact-item .ci-unread-badge {
background: #5856d6;
color: #fff;
font-size: 10px;
font-weight: bold;
padding: 2px 6px;
border-radius: 10px;
margin-left: 6px;
display: inline-block;
vertical-align: middle;
}
/* ========================================================= */
/* HEADER SEARCH DROPDOWN */
/* ========================================================= */
.search-dropdown {
position: absolute;
top: calc(100% + 6px);
left: 0;
width: 420px;
background: #ffffff;
border: 1px solid #e8e8e8;
border-radius: 12px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.06);
z-index: 9999;
overflow: hidden;
animation: searchDropIn 0.2s ease;
}
@keyframes searchDropIn {
from {
opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.search-tabs {
display: flex;
gap: 4px;
padding: 12px 16px 0;
border-bottom: 1px solid #f0f0f0;
}
.search-tab-item {
background: none;
border: none;
padding: 8px 16px;
font-size: 13px;
font-weight: 500;
color: #888;
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.2s ease;
border-radius: 6px 6px 0 0;
outline: none;
}
.search-tab-item:hover {
color: #4B49AC;
background: #f6f5ff;
}
.search-tab-item.active {
color: #4B49AC;
border-bottom-color: #4B49AC;
font-weight: 600;
}
.search-results {
max-height: 360px;
overflow-y: auto;
padding: 8px 0;
}
.search-results::-webkit-scrollbar {
width: 4px;
}
.search-results::-webkit-scrollbar-track {
background: transparent;
}
.search-results::-webkit-scrollbar-thumb {
background: #ddd;
border-radius: 4px;
}
.search-section-title {
padding: 8px 16px 4px;
font-size: 10px;
font-weight: 700;
color: #aaa;
letter-spacing: 1px;
text-transform: uppercase;
}
.search-result-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
cursor: pointer;
transition: background 0.15s ease;
text-decoration: none;
color: inherit;
}
.search-result-item:hover {
background: #f8f7ff;
text-decoration: none;
color: inherit;
}
.search-result-icon {
width: 38px;
height: 38px;
border-radius: 10px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.search-result-icon i {
font-size: 16px;
color: #101112ff;
}
.search-result-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
border: 2px solid #f0f0f0;
}
.search-result-info {
flex: 1;
min-width: 0;
}
.search-result-name {
font-size: 13.5px;
font-weight: 600;
color: #2c2e33;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.3;
}
.search-result-sub {
font-size: 11.5px;
color: #999;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.3;
}
.search-result-arrow {
color: #ccc;
font-size: 14px;
flex-shrink: 0;
transition: color 0.15s;
}
.search-result-item:hover .search-result-arrow {
color: #4B49AC;
}
.search-empty {
text-align: center;
padding: 30px 16px;
color: #bbb;
font-size: 13px;
}
.search-highlight {
background: #e8e5ff;
border-radius: 2px;
padding: 0 1px;
}
</style>
<script>
function formatRelativeHeader(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
const now = new Date();
const diffMs = now - date;
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 60) return diffMins + ' Min';
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return diffHours + ' Hour';
const diffDays = Math.floor(diffHours / 24);
return diffDays + ' Day';
}
document.addEventListener('DOMContentLoaded', function () {
let headerChatPollTimer;
let headerWarningPollTimer;
function loadHeaderWarnings() {
fetch('api_notifications.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'action=get_warnings'
})
.then(r => r.json())
.then(res => {
if (res.status === 'success') {
// Update unread danger count
let unreadCount = parseInt(res.danger_unread) || 0;
let indicator = document.getElementById('headerWarningCountIndicator');
let badge = document.getElementById('headerWarningBadge');
if (unreadCount > 0) {
if (indicator) indicator.style.display = 'block';
if (badge) badge.innerText = unreadCount + ' Baru';
} else {
if (indicator) indicator.style.display = 'none';
if (badge) badge.innerText = '0';
}
// Build list
let html = '';
let warnings = res.warnings || [];
if (warnings.length > 0) {
warnings.forEach(w => {
let timeDiff = formatRelativeHeader(w.time);
html += `
<a class="dropdown-item preview-item" style="padding: 12px 20px; border-bottom: 1px solid #f9f9f9;" href="notifications.php">
<div class="preview-thumbnail">
<div class="preview-icon bg-danger" style="width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: 50%;">
<i class="mdi ${w.icon.replace('mdi-', '')} mx-0 text-white" style="font-size: 18px;"></i>
</div>
</div>
<div class="preview-item-content" style="overflow: hidden; text-overflow: ellipsis; padding-left: 12px;">
<h6 class="preview-subject font-weight-normal text-danger mb-1" style="font-size: 13px; font-weight: 600 !important; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${w.title}</h6>
<p class="font-weight-light small-text mb-0 text-muted" style="font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
${w.message}
</p>
<span class="text-xs text-muted" style="font-size: 10px;"><i class="mdi mdi-clock-outline mr-1"></i>${timeDiff} lalu</span>
</div>
</a>`;
});
} else {
html = '<div class="p-4 text-center text-muted"><i class="mdi mdi-bell-off-outline mb-2" style="font-size: 24px; color: #ddd;"></i><br><span style="font-size: 12px;">Tidak ada peringatan baru.</span></div>';
}
let listContainer = document.getElementById('headerWarningList');
if (listContainer) {
listContainer.innerHTML = html;
}
}
})
.catch(err => console.error("Error fetching warnings for header: ", err));
}
function loadHeaderChats() {
fetch('chat_api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'action=get_contacts'
})
.then(response => response.json())
.then(res => {
if (res.status === 'success') {
let html = '';
const nowServer = new Date();
// Filter hanya kontak dengan pesan yang belum terbaca
let unreadContacts = res.data.filter(c => c.unread_count > 0);
unreadContacts.slice(0, 10).forEach(c => {
let rawLastMsg = c.last_message || '';
let strippedMsg = rawLastMsg.replace(/<[^>]*>?/gm, '').trim();
if (!strippedMsg && rawLastMsg.includes('<img')) strippedMsg = '📷 Foto';
if (!strippedMsg && rawLastMsg.includes('mdi-file-document')) strippedMsg = '📄 Dokumen';
if (!strippedMsg && rawLastMsg.includes('<video')) strippedMsg = '🎥 Video';
if (!strippedMsg && rawLastMsg.includes('<audio')) strippedMsg = '🎵 Audio';
let lastMsg = strippedMsg || (rawLastMsg ? 'Lampiran' : 'Start a conversation');
let timeStr = c.last_time ? formatRelativeHeader(c.last_time) : '';
let isOnline = false;
if (c.last_active) {
let lastActStr = c.last_active.replace(/-/g, '/');
let actDate = new Date(lastActStr);
if ((nowServer - actDate) / 1000 < 20) {
isOnline = true;
}
}
let statusColor = isOnline ? '#10b981' : '#f59e0b';
let unreadBadge = `<span class="ci-unread-badge">${c.unread_count}</span>`;
let nameStyle = 'font-weight: 800; color: #1e2223;';
let msgStyle = 'font-weight: 700; color: #222;';
html += `
<a href="chats.php?id=${c.id_occupant}" class="contact-item">
<div class="ci-avatar">
<img src="${c.foto}" alt="${c.name}">
<div class="ci-status" style="background:${statusColor}"></div>
</div>
<div class="ci-info">
<div class="ci-header">
<div class="ci-name" style="${nameStyle}">${c.name}${unreadBadge}</div>
<div class="ci-time">${timeStr}</div>
</div>
<div class="ci-msg" style="${msgStyle}">${lastMsg}</div>
</div>
</a>
`;
});
if (!html) {
html = '<div style="text-align:center; padding:30px; color:#999; font-size:13px;">Tidak ada pesan baru.</div>';
}
document.getElementById('headerChatsList').innerHTML = html;
}
})
.catch(err => console.error("Chat polling error", err));
}
function startHeaderPolls() {
loadHeaderChats();
loadHeaderWarnings(); // Initial load
headerChatPollTimer = setInterval(loadHeaderChats, 5000);
headerWarningPollTimer = setInterval(loadHeaderWarnings, 10000); // Polling warnings every 10s
}
startHeaderPolls();
});
</script>
<script>
(function () {
const searchInput = document.getElementById('navbar-search-input');
const searchDropdown = document.getElementById('searchDropdown');
const searchResults = document.getElementById('searchResults');
const searchTabs = document.getElementById('searchTabs');
let debounceTimer = null;
let activeTab = 'all';
let cachedData = { overview: [], account: [], occupant: [] };
function highlightText(text, query) {
if (!query) return text;
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp('(' + escaped + ')', 'gi');
return text.replace(regex, '<span class="search-highlight">$1</span>');
}
function renderResults(data, query, tab) {
cachedData = data;
let html = '';
const showOverview = tab === 'all' || tab === 'overview';
const showAccount = tab === 'all' || tab === 'account';
const showOccupant = tab === 'all' || tab === 'occupant';
// Overview
if (showOverview && data.overview && data.overview.length > 0) {
html += '<div class="search-section-title">Menu</div>';
data.overview.forEach(item => {
html += `<a href="${item.url}" class="search-result-item">
<div class="search-result-icon"><i class="${item.icon}"></i></div>
<div class="search-result-info">
<div class="search-result-name">${highlightText(item.name, query)}</div>
<div class="search-result-sub">${item.category}</div>
</div>
<i class="ti-angle-right search-result-arrow"></i>
</a>`;
});
}
// Account
if (showAccount && data.account && data.account.length > 0) {
html += '<div class="search-section-title">Account</div>';
data.account.forEach(item => {
html += `<a href="operations_account.php" class="search-result-item">
<img src="${item.foto}" alt="${item.name}" class="search-result-avatar">
<div class="search-result-info">
<div class="search-result-name">${highlightText(item.name, query)}</div>
<div class="search-result-sub">@${highlightText(item.username, query)}</div>
</div>
<i class="ti-angle-right search-result-arrow"></i>
</a>`;
});
}
// Occupant
if (showOccupant && data.occupant && data.occupant.length > 0) {
html += '<div class="search-section-title">Occupant</div>';
data.occupant.forEach(item => {
const sub = item.no_hp ? item.no_hp : 'No phone';
html += `<a href="manage_occupant.php" class="search-result-item">
<img src="${item.foto}" alt="${item.name}" class="search-result-avatar">
<div class="search-result-info">
<div class="search-result-name">${highlightText(item.name, query)}</div>
<div class="search-result-sub">${highlightText(sub, query)}</div>
</div>
<i class="ti-angle-right search-result-arrow"></i>
</a>`;
});
}
if (!html) {
html = '<div class="search-empty">No results found</div>';
}
searchResults.innerHTML = html;
}
function doSearch(query) {
if (!query || query.length < 1) {
searchDropdown.style.display = 'none';
return;
}
searchDropdown.style.display = 'block';
searchResults.innerHTML = '<div class="search-empty"><i class="icon-search" style="font-size:18px;opacity:0.3;"></i><br>Searching...</div>';
fetch('search_api.php?q=' + encodeURIComponent(query))
.then(r => r.json())
.then(res => {
if (res.status === 'success') {
renderResults(res.data, query, activeTab);
}
})
.catch(err => {
searchResults.innerHTML = '<div class="search-empty">Error searching</div>';
});
}
// Input event with debounce
searchInput.addEventListener('input', function () {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
doSearch(this.value.trim());
}, 300);
});
// Focus shows dropdown if there's content
searchInput.addEventListener('focus', function () {
if (this.value.trim().length >= 1) {
doSearch(this.value.trim());
}
});
// Tab clicks
searchTabs.addEventListener('click', function (e) {
const btn = e.target.closest('.search-tab-item');
if (!btn) return;
searchTabs.querySelectorAll('.search-tab-item').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
activeTab = btn.getAttribute('data-tab');
const query = searchInput.value.trim();
if (query) {
renderResults(cachedData, query, activeTab);
}
});
// Close on Escape
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
searchDropdown.style.display = 'none';
}
});
// Close on click outside
document.addEventListener('click', function (e) {
if (!searchDropdown.contains(e.target) && e.target !== searchInput && !e.target.closest('#navbar-search-icon')) {
searchDropdown.style.display = 'none';
}
});
})();
</script>