TKK_E32230273/chats.php

1753 lines
59 KiB
PHP

<?php include 'header.php'; ?>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
/* CSS for Chat Interface */
.chat-wrapper {
display: flex;
height: 600px;
background: #fff;
border-radius: 12px;
overflow: hidden;
position: relative;
}
/* Custom Delete Modal */
.del-overlay { display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.35); z-index:10000; align-items:center; justify-content:center; }
.del-overlay.show { display:flex; }
.del-box { background:#fff; border-radius:14px; width:260px; overflow:hidden; box-shadow:0 10px 40px rgba(0,0,0,0.18); animation: delPop 0.15s ease-out; }
@keyframes delPop { from { transform:scale(0.92); opacity:0; } to { transform:scale(1); opacity:1; } }
.del-title { padding:18px 20px 14px; text-align:center; font-size:14px; font-weight:700; color:#1a1a1a; border-bottom:1px solid #f0f0f0; }
.del-option { padding:12px 20px; text-align:center; font-size:13px; font-weight:500; cursor:pointer; transition:background 0.12s; border-bottom:1px solid #f0f0f0; }
.del-option:last-child { border-bottom:none; }
.del-option:hover { background:#f8f9fa; }
.del-option.del-red { color:#ef4444; }
.del-option.del-amber { color:#f59e0b; }
.del-option.del-cancel { color:#64748b; }
.chat-sidebar {
width: 340px;
border-right: 1px solid #f0f0f0;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.chat-main {
flex: 1;
display: flex;
flex-direction: column;
background: #fafbfe;
position: relative;
}
.chat-search-header {
padding: 18px 20px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
gap: 12px;
}
.chat-search-header img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.chat-search-box {
flex: 1;
position: relative;
}
.chat-search-box input {
width: 100%;
padding: 8px 16px 8px 36px;
border-radius: 20px;
border: 1px solid #e0e0e0;
font-size: 13px;
outline: none;
transition: 0.2s;
}
.chat-search-box input:focus {
border-color: #5856d6;
box-shadow: 0 0 0 3px rgba(88, 86, 214, 0.1);
}
.chat-search-box i {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #999;
font-size: 14px;
}
.chat-list-wrapper {
flex: 1;
overflow-y: auto;
}
.chat-list-wrapper::-webkit-scrollbar { width: 6px; }
.chat-list-wrapper::-webkit-scrollbar-thumb { background: #ddd; border-radius: 4px; }
.section-title {
padding: 16px 20px 8px;
font-size: 14px;
font-weight: 600;
color: #5856d6;
}
.contact-item {
display: flex;
padding: 12px 20px;
gap: 12px;
cursor: pointer;
transition: 0.2s;
align-items: center;
}
.contact-item:hover {
background: #f8faff;
}
.contact-item.active {
background: #5856d6;
}
.contact-item.active .ci-name { color: #fff; }
.contact-item.active .ci-msg { color: #e0e7ff; }
.contact-item.active .ci-time { color: #c7d2fe; }
.ci-avatar {
width: 42px;
height: 42px;
border-radius: 50%;
position: relative;
}
.ci-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.ci-status {
position: absolute;
bottom: 2px;
right: 2px;
width: 10px;
height: 10px;
border-radius: 50%;
background: #10b981;
border: 2px solid #fff;
}
.contact-item.active .ci-status { border-color: #5856d6; }
.ci-info {
flex: 1;
min-width: 0;
}
.ci-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 4px;
}
.ci-name {
font-size: 14px;
color: #333;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ci-time {
font-size: 11px;
color: #999;
flex-shrink: 0;
}
.ci-msg {
font-size: 12px;
color: #666;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ci-unread-badge {
background: #5856d6;
color: #fff;
font-size: 9px;
font-weight: bold;
padding: 1.5px 5px;
border-radius: 8px;
display: inline-block;
min-width: 14px;
text-align: center;
}
/* Right Blank View */
.chat-blank {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #666;
}
.chat-blank-icon {
width: 100px;
height: 100px;
border-radius: 50%;
background: #eff6ff;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.chat-blank-icon i { font-size: 40px; color: #5856d6; }
/* Right Active View */
.chat-active-view {
display: none;
flex-direction: column;
height: 100%;
}
.chat-header {
padding: 16px 24px;
background: #fff;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.ch-left {
display: flex;
align-items: center;
gap: 12px;
}
.ch-left img {
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
}
.ch-name {
font-size: 15px;
font-weight: 600;
color: #333;
margin-bottom: 2px;
}
.ch-status {
font-size: 12px;
color: #666;
}
.ch-right {
display: flex;
gap: 16px;
color: #666;
}
.ch-right i {
font-size: 18px;
cursor: pointer;
transition: 0.2s;
}
.ch-right i:hover { color: #5856d6; }
.chat-history {
flex: 1;
padding: 24px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 20px;
}
.chat-history::-webkit-scrollbar { width: 6px; }
.chat-history::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; }
.msg-row {
display: flex;
align-items: flex-end;
gap: 8px;
max-width: 80%;
}
.msg-row img {
width: 28px;
height: 28px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.msg-content {
display: flex;
flex-direction: column;
}
.msg-bubble {
padding: 6px 7px 4px 9px;
border-radius: 7.5px;
font-size: 14.2px;
line-height: 19px;
box-shadow: 0 1px .5px rgba(11,20,26,.13);
position: relative;
color: #111b21;
word-break: break-all; /* Force long URLs/strings to break anywhere */
white-space: pre-wrap;
}
.msg-bubble p {
margin-bottom: 0;
margin-top: 0;
}
.msg-time {
font-size: 11px;
color: #667781;
display: inline-flex;
align-items: center;
float: right;
margin-top: 4px;
margin-bottom: -4px;
margin-left: 12px;
}
/* WhatsApp-like message status */
.msg-status { display: inline-flex; align-items: center; margin-left: 3px; }
.msg-status .mdi { font-size: 15.5px; line-height: 1; margin-top: 1px;}
.msg-status-sending .mdi { color: #8696a0; animation: spinCheck 1s linear infinite; font-size: 13px;}
.msg-status-sent .mdi { color: #8696a0; }
.msg-status-delivered .mdi { color: #8696a0; }
.msg-status-read .mdi { color: #53bdeb; }
@keyframes spinCheck { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
/* Optimistic message (temporary) */
.msg-row.msg-sending { opacity: .7; }
.msg-row.msg-sending .msg-bubble { position: relative; }
/* Occupant Msg (Left) */
.msg-left {
align-self: flex-start;
}
.msg-left .msg-bubble {
background: #ffffff;
border-top-left-radius: 0;
}
.msg-left .msg-time { color: #667781; }
/* Admin Msg (Right) - Updated to Primary Color */
.msg-right {
align-self: flex-end;
flex-direction: row-reverse;
}
.msg-right .msg-bubble {
background: #5856d6;
color: #ffffff;
border-top-right-radius: 0;
}
.msg-right .msg-time { color: #667781; }
.msg-right .msg-status .mdi { color: #8696a0 !important; }
.msg-right .msg-status-read .mdi { color: #53bdeb !important; }
.edit-tag {
float: right !important;
margin-top: 2px !important;
margin-left: 8px !important;
font-size: 11.5px !important;
}
.msg-right .edit-tag { color: rgba(255,255,255,0.8) !important; }
.msg-left .edit-tag { color: #8696a0 !important; }
/* Chat Footer */
.chat-footer {
padding: 16px 24px;
background: #fff;
border-top: 1px solid #f0f0f0;
display: flex;
flex-direction: column;
}
.chat-footer-top {
display: flex;
align-items: center;
gap: 16px;
width: 100%;
}
.reply-preview-box {
display: none;
width: 100%;
margin-bottom: 12px;
padding: 10px 14px;
background: #f8fafc;
border-left: 4px solid #5856d6;
border-radius: 6px;
position: relative;
font-size: 13px;
}
.rp-close {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #94a3b8;
font-size: 18px;
}
.rp-close:hover { color: #ef4444; }
.rp-title { font-weight: bold; color: #5856d6; margin-bottom: 2px;}
.rp-text { color: #64748b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 20px;}
.msg-reply-block {
background: rgba(0,0,0,0.05);
border-left: 3px solid #5856d6;
padding: 6px 10px;
border-radius: 4px;
margin-bottom: 6px;
font-size: 12px;
cursor: pointer;
}
.msg-reply-block .replier { font-weight: bold; color: #5856d6; }
.msg-right .msg-reply-block { background: rgba(255,255,255,0.2); border-color: #fff; }
.msg-right .msg-reply-block .replier { color: #fff; }
.chat-footer .input-wrapper {
flex: 1;
background: #f8fafc;
border-radius: 20px;
border: 1px solid #e2e8f0;
display: flex;
align-items: flex-end; /* Align to bottom for textarea growth */
padding: 8px 16px;
transition: 0.2s;
}
.chat-footer .input-wrapper:focus-within {
border-color: #5856d6;
background: #fff;
box-shadow: 0 0 0 3px rgba(88, 86, 214, 0.1);
}
.chat-footer textarea {
flex: 1;
border: none;
background: transparent;
outline: none;
padding: 8px;
font-size: 14px;
color: #333;
font-family: inherit;
resize: none;
max-height: 120px;
overflow-y: auto;
box-sizing: border-box;
/* Hide scrollbar for cleaner look */
-ms-overflow-style: none;
scrollbar-width: none;
}
.chat-footer textarea::-webkit-scrollbar {
display: none;
}
#btnEmoji {
background: transparent;
border: none;
outline: none;
box-shadow: none;
color: #64748b;
font-size: 20px;
padding: 0;
margin: 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
#btnEmoji:hover {
color: #5856d6;
}
.btn-send {
background: #5856d6;
color: #fff;
border: none;
border-radius: 8px;
padding: 10px 20px;
font-weight: 600;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
transition: 0.2s;
box-shadow: none;
}
.btn-send:hover { background: #4b49ac; transform: translateY(-1px); }
/* Emoji Picker */
.emoji-picker {
display: none; position: absolute; bottom: 70px; right: 24px;
width: 320px; background: #fff; border-radius: 12px;
box-shadow: 0 8px 24px rgba(0,0,0,.12); border: 1px solid #edf2f7;
z-index: 100; overflow: hidden;
}
.emoji-picker.show { display: block; }
.emoji-tabs { display: flex; border-bottom: 1px solid #f1f5f9; padding: 0 4px; }
.emoji-tab {
flex: 1; padding: 8px 0; text-align: center; cursor: pointer;
font-size: 16px; opacity: .5; transition: .12s; border-bottom: 2px solid transparent;
}
.emoji-tab:hover { opacity: .8; }
.emoji-tab.active { opacity: 1; border-bottom-color: #5856d6; }
.emoji-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px; padding: 8px; max-height: 200px; overflow-y: auto; }
.emoji-grid::-webkit-scrollbar { width: 4px; }
.emoji-grid::-webkit-scrollbar-thumb { background: #ddd; border-radius: 4px; }
.emoji-item { font-size: 20px; text-align: center; padding: 6px; cursor: pointer; border-radius: 6px; transition: .1s; }
.emoji-item:hover { background: #f1f5f9; transform: scale(1.2); }
/* Custom Context Menu */
.chat-context-menu {
position: fixed; z-index: 9999; width: 180px; background: #fff;
border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,.15);
padding: 6px 0; display: none; font-size: 13px;
border: 1px solid #edf2f7;
}
.chat-context-menu ul { list-style: none; padding: 0; margin: 0; }
.chat-context-menu li {
display: flex; align-items: center; gap: 10px; padding: 9px 16px;
cursor: pointer; transition: background .12s; color: #334155;
}
.chat-context-menu li:hover { background: #f1f5f9; }
.chat-context-menu li i { font-size: 16px; width: 18px; text-align: center; color: #64748b; }
.chat-context-menu li.ctx-danger { color: #ef4444; }
.chat-context-menu li.ctx-danger i { color: #ef4444; }
.chat-context-menu .ctx-sep { height: 1px; background: #f1f5f9; margin: 4px 0; }
/* Selection Mode */
.msg-checkbox {
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 22px;
color: #94a3b8;
display: none;
cursor: pointer;
z-index: 10;
}
.msg-row { position: relative; transition: 0.2s; }
.msg-row.selectable {
cursor: pointer;
}
/* Admin Chat Checkbox */
.msg-right .msg-checkbox {
left: 15px; /* sedikit ditambah jaraknya */
}
.msg-right.selectable {
padding-left: 45px; /* memberi ruang */
}
/* Occupant Chat Checkbox */
.msg-left .msg-checkbox {
position: static;
transform: none;
margin-left: 15px;
align-self: center;
order: 3;
}
.msg-left.selectable {
padding-left: 0;
}
.msg-row.selectable .msg-checkbox {
display: block;
}
.msg-row.selected .msg-checkbox i::before {
content: "\F0134"; /* mdi-checkbox-marked-circle */
color: #5856d6;
}
.msg-row.selected .msg-bubble {
background: rgba(88, 86, 214, 0.15) !important;
color: #111b21 !important;
}
.msg-row.msg-right.selected .msg-bubble {
background: rgba(88, 86, 214, 0.8) !important;
color: #fff !important;
}
.ch-right-actions {
display: none;
align-items: center;
gap: 15px;
}
.ch-right-actions i {
font-size: 22px;
cursor: pointer;
color: #64748b;
}
.ch-right-actions i.action-del { color: #ef4444; }
.ch-right-actions i.action-close:hover { color: #333; }
/* Dropdown Menu */
.header-dropdown {
position: relative;
display: inline-block;
}
.header-dropdown-content {
display: none;
position: absolute;
right: 0;
top: 30px;
background-color: #fff;
min-width: 150px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1000;
border-radius: 8px;
overflow: hidden;
}
.header-dropdown-content a {
color: #333;
padding: 12px 16px;
text-decoration: none;
display: block;
font-size: 14px;
}
.header-dropdown-content a:hover {
background-color: #f1f5f9;
}
.show-dropdown {
display: block;
}
/* Profile View */
.profile-view {
display: none;
width: 100%;
height: 100%;
background: #fff;
flex-direction: row;
position: absolute;
top: 0;
left: 0;
z-index: 50;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
.pv-left {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border-right: 1px solid #e2e8f0;
}
.pv-left img {
max-width: 80%;
max-height: 80%;
border-radius: 20px;
object-fit: contain;
border: 1px solid #e2e8f0;
}
.pv-right {
flex: 1;
padding: 40px;
display: flex;
flex-direction: column;
justify-content: center;
}
.pv-close {
position: absolute;
top: 20px;
right: 20px;
font-size: 24px;
cursor: pointer;
color: #64748b;
z-index: 51;
}
.pv-close:hover { color: #333; }
.pv-info-row {
margin-bottom: 20px;
}
.pv-label {
font-size: 11px;
color: #94a3b8;
margin-bottom: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
.pv-value {
font-size: 16px;
color: #0f172a;
font-weight: 500;
}
@media (max-width: 768px) {
.profile-view {
flex-direction: column;
overflow-y: auto;
}
.pv-left {
border-right: none;
border-bottom: 1px solid #f0f0f0;
padding: 40px 20px;
flex: none;
}
.pv-left img {
max-width: 200px;
max-height: 200px;
border-radius: 50%;
}
.pv-right {
padding: 20px;
flex: none;
}
}
/* ===== RESPONSIVE MOBILE (WhatsApp-like) ===== */
.chat-back-btn {
display: none;
background: none;
border: none;
font-size: 22px;
color: #5856d6;
cursor: pointer;
padding: 0;
margin-right: 8px;
line-height: 1;
}
@media (max-width: 768px) {
.chat-wrapper {
height: calc(100vh - 120px);
}
.chat-sidebar {
width: 100%;
border-right: none;
}
.chat-main {
display: none !important;
width: 100%;
}
.chat-wrapper.mobile-chat-open .chat-sidebar {
display: none !important;
}
.chat-wrapper.mobile-chat-open .chat-main {
display: flex !important;
width: 100%;
}
.chat-back-btn {
display: inline-flex;
}
.emoji-picker {
width: 280px;
right: 10px;
bottom: 60px;
}
}
</style>
<div class="main-panel">
<div class="content-wrapper">
<div class="row">
<div class="col-md-12 grid-margin stretch-card mb-4 mt-3" style="display:none;">
</div>
<div class="col-md-12 grid-margin stretch-card">
<div class="card" style="border:1px solid #e5e9f0;box-shadow:none;border-radius:12px;">
<div class="card-body" style="padding:0;">
<div class="chat-wrapper">
<!-- Sidebar -->
<div class="chat-sidebar">
<div class="chat-search-header">
<img src="images/admin.png" alt="Admin">
<div class="chat-search-box">
<i class="mdi mdi-magnify"></i>
<input type="text" id="searchInput" placeholder="Search..." onkeyup="filterContacts()">
</div>
</div>
<div class="chat-list-wrapper">
<div id="chatsSection" style="display:none;">
<div class="section-title">Chats</div>
<div id="chatsList"></div>
</div>
</div>
</div>
<!-- Main Chat Area -->
<div class="chat-main">
<!-- Blank State -->
<div class="chat-blank" id="chatBlank">
<div class="chat-blank-icon">
<i class="mdi mdi-message-text-outline"></i>
</div>
<p>Select a contact to start a conversation.</p>
</div>
<!-- Active Chat State -->
<div class="chat-active-view" id="chatActive">
<div class="chat-header">
<div class="ch-left">
<button class="chat-back-btn" id="btnBackToContacts" onclick="backToContacts()" title="Back">
<i class="mdi mdi-arrow-left"></i>
</button>
<img src="" id="activeAvatar" alt="Contact Avatar">
<div>
<div class="ch-name" id="activeName"></div>
<div class="ch-status" id="activeStatus">Occupant</div>
</div>
</div>
<div class="ch-right">
<div id="normalHeaderActions" class="header-dropdown">
<i class="mdi mdi-dots-vertical" onclick="toggleHeaderDropdown()"></i>
<div id="headerDropdownMenu" class="header-dropdown-content">
<a href="javascript:void(0)" onclick="openProfileView()">Profile</a>
<a href="javascript:void(0)" onclick="deleteAllChats()" style="color:#ef4444;">Delete all chats</a>
</div>
</div>
<div id="selectionHeaderActions" class="ch-right-actions">
<span id="selectionCount" style="font-size:15px; font-weight:bold; color:#5856d6;">0</span>
<i class="mdi mdi-delete action-del" title="Delete" onclick="triggerBulkDelete()"></i>
<i class="mdi mdi-close action-close" title="Cancel" onclick="exitSelectionMode()"></i>
</div>
</div>
</div>
<div class="chat-history" id="chatHistory">
<!-- JS will populate -->
</div>
<div class="chat-footer">
<div class="reply-preview-box" id="replyPreview">
<div class="rp-title" id="rpTitle">Replying to User</div>
<div class="rp-text" id="rpText">...</div>
<i class="mdi mdi-close rp-close" onclick="closeReplyBox()"></i>
</div>
<div class="reply-preview-box" id="editPreview" style="border-left-color: #f59e0b;">
<div class="rp-title" style="color: #f59e0b;"><i class="mdi mdi-pencil" style="font-size:14px; margin-right:4px;"></i> Editing Message...</div>
<div class="rp-text" id="epText">...</div>
<i class="mdi mdi-close rp-close" onclick="cancelEdit()"></i>
</div>
<div class="chat-footer-top" id="footerNormal">
<div class="input-wrapper">
<textarea id="messageInput" rows="1" placeholder="Type a message..." onkeydown="handleEnter(event)"></textarea>
<button class="icon-btn" id="btnEmoji" onclick="toggleEmoji()" style="margin-bottom: 4px;"><i class="mdi mdi-emoticon-outline"></i></button>
</div>
<button class="btn-send" onclick="submitMessageOrEdit()" style="align-self: flex-end; margin-bottom: 4px;">
<i class="mdi mdi-send"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Profile View Overlay -->
<div class="profile-view" id="profileView">
<i class="mdi mdi-close pv-close" onclick="closeProfileView()" title="Close Profile"></i>
<div class="pv-left">
<img src="" id="pvImage" alt="Profile Image">
</div>
<div class="pv-right">
<h3 style="margin-bottom: 30px; color: #1e293b; font-weight: 600;">Occupant Profile</h3>
<div class="pv-info-row">
<div class="pv-label">Name</div>
<div class="pv-value" id="pvName"></div>
</div>
<div class="pv-info-row">
<div class="pv-label">Phone Number</div>
<div class="pv-value" id="pvPhone"></div>
</div>
<div class="pv-info-row">
<div class="pv-label">NIK / NIP</div>
<div class="pv-value" id="pvNik"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Emoji Picker -->
<div class="emoji-picker" id="emojiPicker">
<div class="emoji-tabs">
<div class="emoji-tab active" data-cat="smileys">😊</div>
<div class="emoji-tab" data-cat="gestures">👍</div>
<div class="emoji-tab" data-cat="hearts">❤️</div>
<div class="emoji-tab" data-cat="objects">🎉</div>
<div class="emoji-tab" data-cat="animals">🐱</div>
<div class="emoji-tab" data-cat="food">🍕</div>
</div>
<div class="emoji-grid" id="emojiGrid"></div>
</div>
<!-- Context Menu -->
<div id="msgContextMenu" class="chat-context-menu">
<ul>
<li onclick="prepareReply(event)"><i class="mdi mdi-reply"></i> Reply</li>
<li id="ctxMenuEdit" onclick="editSelectedMessage(event)" style="display:none;"><i class="mdi mdi-pencil-outline"></i> Edit</li>
<li onclick="enterSelectionMode(event)"><i class="mdi mdi-checkbox-marked-outline"></i> Select</li>
<div class="ctx-sep"></div>
<li class="ctx-danger" onclick="deleteSelectedMessage(event)"><i class="mdi mdi-delete-outline"></i> Delete</li>
</ul>
</div>
<!-- Custom Delete Modal -->
<div class="del-overlay" id="delOverlay">
<div class="del-box">
<div class="del-title" id="delTitle"></div>
<div id="delOptions"></div>
</div>
</div>
<!-- ==================== MODAL: DELETE ALL CHATS ==================== -->
<div class="modal fade" id="modalDeleteAllChats" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content mdl-card" style="border: none; border-radius: 6px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);">
<form id="formDeleteAllChats" onsubmit="executeDeleteAllChats(event)">
<div class="modal-body p-4" style="text-align: left;">
<h5 class="font-weight-bold mb-3">Delete All Chats</h5>
<p style="font-size:14px;color:#666;margin-bottom:20px;">Are you sure you want to delete all chats with <strong id="deleteAll_occupant_name">this occupant</strong>?</p>
<div class="d-flex justify-content-end align-items-center" style="gap:20px;">
<span style="font-size:14px; font-weight:500; color:#1e293b; cursor:pointer;" data-dismiss="modal">Cancel</span>
<span style="font-size:14px; font-weight:500; color:#ef4444; cursor:pointer;" onclick="executeDeleteAllChats(event)">Delete</span>
</div>
</div>
</form>
</div>
</div>
</div>
<style>
.delete-confirm-cb:checked ~ .custom-control-label::before {
background-color: #ff4747 !important;
border-color: #ff4747 !important;
}
</style>
<script>
function showDeleteModal(title, options) {
document.getElementById('delTitle').textContent = title;
let optHtml = '';
options.forEach(function(o) {
optHtml += '<div class="del-option ' + o.cls + '" id="del-opt-' + Math.random().toString(36).substr(2,5) + '">' + o.text + '</div>';
});
optHtml += '<div class="del-option del-cancel">Cancel</div>';
document.getElementById('delOptions').innerHTML = optHtml;
// Bind events
let optEls = document.getElementById('delOptions').querySelectorAll('.del-option');
let idx = 0;
options.forEach(function(o) {
optEls[idx].onclick = o.action;
idx++;
});
// Cancel = last one
optEls[optEls.length - 1].onclick = closeDeleteModal;
document.getElementById('delOverlay').classList.add('show');
}
function closeDeleteModal() {
document.getElementById('delOverlay').classList.remove('show');
}
// Close on overlay background click
document.getElementById('delOverlay').addEventListener('click', function(e) {
if (e.target === this) closeDeleteModal();
});
</script>
<?php include 'footer.php'; ?>
<script>
let contacts = [];
let activeOccupantId = null;
let activeAvatarUrl = '';
let activeOccupantData = null;
function formatTime(dateStr) {
if(!dateStr) return '';
const date = new Date(dateStr);
let hours = date.getHours();
let minutes = date.getMinutes();
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12;
minutes = minutes < 10 ? '0'+minutes : minutes;
return hours + ':' + minutes + ' ' + ampm;
}
function formatRelative(dateStr) {
if(!dateStr) return '';
const date = new Date(dateStr);
const now = new Date();
const isToday = date.toDateString() === now.toDateString();
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);
const isYesterday = date.toDateString() === yesterday.toDateString();
let hours = date.getHours();
let minutes = date.getMinutes();
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
hours = hours ? hours : 12;
minutes = minutes < 10 ? '0' + minutes : minutes;
const timeStr = hours + ':' + minutes + ' ' + ampm;
if (isToday) return timeStr;
if (isYesterday) return 'Yesterday';
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
if (diffDays < 7) {
const days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
return days[date.getDay()];
}
const dd = String(date.getDate()).padStart(2, '0');
const mm = String(date.getMonth() + 1).padStart(2, '0');
const yyyy = date.getFullYear();
return dd + '/' + mm + '/' + yyyy;
}
function loadContacts() {
$.post('chat_api.php', { action: 'get_contacts' }, function(res) {
if(res.status === 'success') {
contacts = res.data;
renderContacts(contacts);
}
}, 'json');
}
function renderContacts(data) {
let chatsHtml = '';
let contactsHtml = '';
const nowServer = new Date();
data.forEach(c => {
let activeClass = (c.id_occupant == activeOccupantId) ? 'active' : '';
// Cek jika pesan berisi media sebelumnya dicadangkan sbg teks dummy
let rawLastMsg = c.last_message || '';
let strippedMsg = rawLastMsg.replace(/<[^>]*>?/gm, '').trim();
if(!strippedMsg && rawLastMsg.includes('<img')) strippedMsg = '📷 Photo';
if(!strippedMsg && rawLastMsg.includes('mdi-file-document')) strippedMsg = '📄 Document';
if(!strippedMsg && rawLastMsg.includes('<video')) strippedMsg = '🎥 Video';
if(!strippedMsg && rawLastMsg.includes('<audio')) strippedMsg = '🎵 Audio';
let lastMsg = strippedMsg || (rawLastMsg ? 'Attachment' : 'Start a conversation');
let timeStr = c.last_time ? formatRelative(c.last_time) : '';
let isOnline = false;
if (c.last_active) {
let lastActStr = c.last_active.replace(/-/g, '/');
let actDate = new Date(lastActStr);
let diffSec = (nowServer - actDate) / 1000;
if (diffSec < 20) {
isOnline = true;
}
}
let statusColor = isOnline ? '#10b981' : '#f59e0b';
let unreadBadge = '';
let nameStyle = 'font-weight: 600;';
let msgStyle = '';
if (c.unread_count > 0 && c.id_occupant != activeOccupantId) {
unreadBadge = `<span class="ci-unread-badge">${c.unread_count}</span>`;
nameStyle = 'font-weight: 800; color: #1e2223;';
msgStyle = 'font-weight: 700; color: #222;';
}
let verifiedColor = (c.id_occupant == activeOccupantId) ? '#fff' : '#1da1f2';
let isVerified = c.name && c.nik_nip && c.no_hp && c.telegram_id && c.pin && c.foto && c.finger_id && c.code_register && c.progress === 'used' && c.status === 'approved' && c.access === 'active';
let verifiedIcon = isVerified ? `<i class="mdi mdi-check-decagram" style="color: ${verifiedColor}; font-size: 14px; margin-left: 4px; vertical-align: middle;" title="Verified Approved"></i>` : '';
let html = `
<div class="contact-item ${activeClass}" onclick="openChat(${c.id_occupant})">
<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}; display:inline-flex; align-items:center; width:100%;">${c.name} ${verifiedIcon}</div>
<div class="ci-time">${timeStr}</div>
</div>
<div style="display:flex; justify-content:space-between; align-items:center; gap:4px;">
<div class="ci-msg" style="${msgStyle}; flex:1;">${lastMsg}</div>
${unreadBadge}
</div>
</div>
</div>
`;
chatsHtml += html;
});
if(chatsHtml) {
document.getElementById('chatsList').innerHTML = chatsHtml;
document.getElementById('chatsSection').style.display = 'block';
} else {
document.getElementById('chatsList').innerHTML = '<div style="text-align:center; padding:20px; color:#999; font-size:13px;">No active chats.</div>';
document.getElementById('chatsSection').style.display = 'block';
}
}
function filterContacts() {
let q = document.getElementById('searchInput').value.toLowerCase();
let filtered = contacts.filter(c => c.name.toLowerCase().includes(q));
renderContacts(filtered);
}
function getOccupantStatusString(occupant) {
let occStatStr = 'Status unknown';
if (occupant && occupant.last_active) {
let lastActStr = occupant.last_active.replace(/-/g, '/');
let actDate = new Date(lastActStr);
let diffSec = (new Date() - actDate) / 1000;
let diffDays = diffSec / (60 * 60 * 24);
if (diffSec < 20) {
occStatStr = 'Online';
} else if (diffDays > 7) {
let jam = actDate.getHours().toString().padStart(2, '0');
let menit = actDate.getMinutes().toString().padStart(2, '0');
let tgl = actDate.getDate().toString().padStart(2, '0');
let bln = (actDate.getMonth() + 1).toString().padStart(2, '0');
let thn = actDate.getFullYear();
occStatStr = `Last Online ${jam}.${menit} ${tgl}-${bln}-${thn}`;
} else {
const hariArr = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
let namaHari = hariArr[actDate.getDay()];
let jam = actDate.getHours().toString().padStart(2, '0');
let menit = actDate.getMinutes().toString().padStart(2, '0');
occStatStr = `Last Online at ${jam}:${menit} on ${namaHari}`;
}
}
return occStatStr;
}
function openChat(id) {
activeOccupantId = id;
renderContacts(contacts);
document.getElementById('chatBlank').style.display = 'none';
document.getElementById('chatActive').style.display = 'flex';
// Mobile: switch to chat view
document.querySelector('.chat-wrapper').classList.add('mobile-chat-open');
document.getElementById('chatHistory').innerHTML = '<div style="text-align:center; padding:20px; color:#999;">Loading...</div>';
$.post('chat_api.php', { action: 'get_messages', id_occupant: id }, function(res) {
if(res.status === 'success') {
let occ = res.occupant;
activeOccupantData = occ;
let isVerifiedActive = occ.name && occ.nik_nip && occ.no_hp && occ.telegram_id && occ.pin && occ.foto && occ.finger_id && occ.code_register && occ.progress === 'used' && occ.status === 'approved' && occ.access === 'active';
let activeVerifiedIcon = isVerifiedActive ? `<i class="mdi mdi-check-decagram" style="color: #1da1f2; font-size: 16px; margin-left: 4px; vertical-align: middle;" title="Verified Approved"></i>` : '';
document.getElementById('activeName').innerHTML = res.occupant.name + activeVerifiedIcon;
document.getElementById('activeStatus').innerText = getOccupantStatusString(res.occupant);
document.getElementById('activeAvatar').src = res.occupant.foto;
activeAvatarUrl = res.occupant.foto;
renderMessages(res.data);
loadContacts();
}
}, 'json');
}
function backToContacts() {
document.querySelector('.chat-wrapper').classList.remove('mobile-chat-open');
activeOccupantId = null;
document.getElementById('chatBlank').style.display = 'flex';
document.getElementById('chatActive').style.display = 'none';
renderContacts(contacts);
}
function buildMessagesHtml(messages) {
let html = '';
const adminAvatar = 'images/admin.png';
messages.forEach(msg => {
let isRight = (msg.sender === 'account');
let cls = isRight ? 'msg-right' : 'msg-left';
let statusHtml = '';
if (isRight) {
if (msg.is_read == 1) {
statusHtml = '<span class="msg-status msg-status-read"><i class="mdi mdi-check-all"></i></span>';
} else if (msg.telegram_msg_id) {
statusHtml = '<span class="msg-status msg-status-delivered"><i class="mdi mdi-check-all"></i></span>';
} else {
statusHtml = '<span class="msg-status msg-status-sent"><i class="mdi mdi-check"></i></span>';
}
}
let avatarToUse = isRight ? adminAvatar : activeAvatarUrl;
let onContextMenu = `oncontextmenu="showContextMenu(event, ${msg.id_chat}, '${msg.sender}', this); return false;"`;
let replyBlock = '';
if (msg.reply_to_chat_id && msg.reply_message) {
let rpSender = (msg.reply_sender === 'account') ? 'You' : document.getElementById('activeName').innerText;
let strippedRpMsg = msg.reply_message.replace(/<[^>]*>?/gm, '');
if(!strippedRpMsg) strippedRpMsg = 'Attachment/File';
replyBlock = `<div class="msg-reply-block" onclick="scrollToMsg(${msg.reply_to_chat_id})">` +
`<div class="replier">${rpSender}</div>` +
`<div style="white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:200px;">${strippedRpMsg}</div>` +
`</div>`;
}
let msgOutput = msg.message;
let timeOutput = `<div class="msg-time">${formatTime(msg.created_at)} ${statusHtml}</div>`;
let bubbleClass = 'msg-bubble';
// Additional Logic for Selection Mode
let selectionClasses = '';
if (isSelectionMode) {
selectionClasses = 'selectable';
if (selectedMessages.some(sm => sm.id === msg.id_chat)) selectionClasses += ' selected';
}
let checkboxHtml = `<div class="msg-checkbox" onclick="toggleSelectMessage(event, ${msg.id_chat}, '${msg.sender}')"><i class="mdi mdi-checkbox-blank-circle-outline"></i></div>`;
let msgRowClick = isSelectionMode ? `onclick="toggleSelectMessage(event, ${msg.id_chat}, '${msg.sender}')"` : '';
html += `
<div class="msg-row ${cls} ${selectionClasses}" id="msg-${msg.id_chat}" data-id="${msg.id_chat}" ${msgRowClick}>
${checkboxHtml}
<img src="${avatarToUse}">
<div class="msg-content">
<div class="${bubbleClass}" ${onContextMenu}>${replyBlock}${msgOutput}</div>
${timeOutput}
</div>
</div>
`;
});
return html;
}
function renderMessages(messages) {
let hist = document.getElementById('chatHistory');
let newHtml = buildMessagesHtml(messages);
if (hist.innerHTML !== newHtml) {
let isScrolledBottom = hist.scrollHeight - hist.clientHeight <= hist.scrollTop + 10;
hist.innerHTML = newHtml;
if (isScrolledBottom || hist.scrollTop === 0) {
scrollToBottom();
}
}
}
function scrollToBottom() {
let hist = document.getElementById('chatHistory');
hist.scrollTop = hist.scrollHeight;
}
function scrollToMsg(id) {
let el = document.getElementById('msg-' + id);
if(el) {
el.scrollIntoView({behavior: "smooth", block: "center"});
el.style.backgroundColor = 'rgba(88, 86, 214, 0.2)';
setTimeout(() => { el.style.backgroundColor = 'transparent'; }, 1000);
}
}
function handleEnter(e) {
if(e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); // Mencegah pindah baris
submitMessageOrEdit();
}
}
// Auto-resize textarea
document.addEventListener('input', function (e) {
if (e.target.id === 'messageInput') {
e.target.style.height = 'auto'; // Reset height
e.target.style.height = (e.target.scrollHeight) + 'px'; // Set to scroll height
}
});
function submitMessageOrEdit() {
if (editingMsgId) {
saveEdit();
} else {
sendMessage();
}
}
function sendMessage() {
let input = document.getElementById('messageInput');
let text = input.value.trim();
if(!text || !activeOccupantId) return;
let formData = new FormData();
formData.append('action', 'send_message');
formData.append('id_occupant', activeOccupantId);
formData.append('message', text);
formData.append('sender', 'account');
if (replyingToId) formData.append('reply_to_chat_id', replyingToId);
let tempId = 'temp-' + Date.now();
let optStatus = '<span class="msg-status msg-status-sending"><i class="mdi mdi-clock-outline"></i></span>';
let timeOutput = `<div class="msg-time">Sending... ${optStatus}</div>`;
let hist = document.getElementById('chatHistory');
let tempRow = document.createElement('div');
tempRow.className = 'msg-row msg-right msg-sending';
tempRow.id = tempId;
let escapedText = text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
tempRow.innerHTML = `
<img src="images/admin.png">
<div class="msg-content">
<div class="msg-bubble">${escapedText}</div>
${timeOutput}
</div>
`;
hist.appendChild(tempRow);
scrollToBottom();
input.value = '';
input.style.height = 'auto'; // Reset textarea height
closeReplyBox();
$.ajax({
url: 'chat_api.php',
type: 'POST',
data: formData,
contentType: false,
processData: false,
dataType: 'json',
success: function(res) {
if(res.status === 'success') {
loadContacts();
$.post('chat_api.php', { action: 'get_messages', id_occupant: activeOccupantId }, function(res2) {
if(res2.status === 'success') {
renderMessages(res2.data);
}
}, 'json');
} else {
let el = document.getElementById(tempId);
if (el) {
el.querySelector('.msg-status').innerHTML = '<i class="mdi mdi-alert-circle" style="color:#ef4444;font-size:14px;" title="Failed to send"></i>';
el.classList.remove('msg-sending');
}
}
},
error: function() {
let el = document.getElementById(tempId);
if (el) {
el.querySelector('.msg-status').innerHTML = '<i class="mdi mdi-alert-circle" style="color:#ef4444;font-size:14px;" title="Failed to send"></i>';
el.classList.remove('msg-sending');
}
}
});
}
// ═══ EMOJI PICKER ═══
const emojiData = {
smileys: ['😀','😃','😄','😁','😆','😅','🤣','😂','🙂','😊','😇','🥰','😍','🤩','😘','😋','😛','😜','🤪','😝','🤑','🤗','🤭','🤫','🤔','🤐','🤨','😐','😑','😶','😏','😒','🙄','😬','😮‍💨','🤥','😌','😔','😪','🤤','😴','😷','🤒','🤕','🤢','🤮','🥵','🥶','😵','🤯','🥳','🥸','😎','🤓','🧐'],
gestures: ['👍','👎','👌','🤌','🤏','✌️','🤞','🤟','🤘','🤙','👈','👉','👆','👇','☝️','✋','🤚','🖐️','🖖','👋','🤝','👏','✍️','🙏','💪','🦾','🫶','🫱','🫲'],
hearts: ['❤️','🧡','💛','💚','💙','💜','🖤','🤍','🤎','💔','❤️‍🔥','❤️‍🩹','💕','💞','💓','💗','💖','💘','💝','💟','♥️','🫀'],
objects: ['🎉','🎊','🎈','🎁','🎀','🏆','🥇','🎵','🎶','🎸','🎹','🎤','🔔','⭐','🌟','💫','✨','⚡','🔥','💥','🎯','💡','📌','📎','✅','❌','⚠️','💬','💭','🗨️'],
animals: ['🐶','🐱','🐭','🐹','🐰','🦊','🐻','🐼','🐨','🐯','🦁','🐮','🐷','🐸','🐵','🐔','🐧','🐦','🦅','🦉','🐴','🦄','🐝','🐛','🦋','🐌','🐞','🐙','🐠','🐳'],
food: ['🍕','🍔','🍟','🌭','🍿','🧀','🥚','🍳','🥞','🧇','🥓','🥩','🍗','🍖','🌮','🌯','🥗','🍜','🍝','🍣','🍤','🍩','🍪','🎂','🍰','🧁','🍫','🍬','🍭','☕','🍵','🥤','🧃','🍷','🍺']
};
function toggleEmoji() {
let picker = document.getElementById('emojiPicker');
picker.classList.toggle('show');
if (picker.classList.contains('show')) renderEmojis('smileys');
}
function renderEmojis(cat) {
let grid = document.getElementById('emojiGrid');
grid.innerHTML = emojiData[cat].map(e => `<div class="emoji-item" onclick="insertEmoji('${e}')">${e}</div>`).join('');
}
function insertEmoji(e) {
let input = document.getElementById('messageInput');
input.value += e;
input.style.height = 'auto';
input.style.height = (input.scrollHeight) + 'px';
input.focus();
}
document.addEventListener('click', function(e) {
if(e.target.classList.contains('emoji-tab')) {
document.querySelectorAll('.emoji-tab').forEach(t => t.classList.remove('active'));
e.target.classList.add('active');
renderEmojis(e.target.dataset.cat);
}
let picker = document.getElementById('emojiPicker');
let btn = document.getElementById('btnEmoji');
if (picker && btn && picker.classList.contains('show') && !picker.contains(e.target) && !btn.contains(e.target)) {
picker.classList.remove('show');
}
});
// Context Menu Logic
let selectedMsgId = null;
let selectedMsgSender = null;
let selectedMsgText = null;
let replyingToId = null;
let editingMsgId = null;
let originalEditMsgText = null;
// Selection Mode State
let isSelectionMode = false;
let selectedMessages = []; // Array of objects: {id, sender}
function showContextMenu(e, idChat, sender, bubbleEl) {
e.preventDefault();
selectedMsgId = idChat;
selectedMsgSender = sender;
// Extract text ignoring the reply block and the (Diedit) tag
let clone = bubbleEl.cloneNode(true);
let replyBlocks = clone.querySelectorAll('.msg-reply-block');
replyBlocks.forEach(b => b.remove());
let editTags = clone.querySelectorAll('i'); // Removes the (Diedit) tag along with other icons
editTags.forEach(t => t.remove());
selectedMsgText = (clone.textContent || '').trim();
let menu = document.getElementById('msgContextMenu');
// Show/hide Edit button based on sender
let btnEdit = document.getElementById('ctxMenuEdit');
if (sender === 'account') {
btnEdit.style.display = 'flex';
} else {
btnEdit.style.display = 'none';
}
let x = e.clientX;
let y = e.clientY;
if (x + 180 > window.innerWidth) x = window.innerWidth - 190;
if (y + 130 > window.innerHeight) y = window.innerHeight - 130;
menu.style.left = x + 'px';
menu.style.top = y + 'px';
menu.style.display = 'block';
}
function prepareReply(event) {
if(event) event.stopPropagation();
replyingToId = selectedMsgId;
let title = (selectedMsgSender === 'account') ? 'Replying to You' : ('Replying to ' + document.getElementById('activeName').innerText);
document.getElementById('rpTitle').innerText = title;
document.getElementById('rpText').innerText = selectedMsgText;
document.getElementById('replyPreview').style.display = 'block';
document.getElementById('messageInput').focus();
hideContextMenu();
}
function closeReplyBox() {
replyingToId = null;
document.getElementById('replyPreview').style.display = 'none';
}
function hideContextMenu() {
document.getElementById('msgContextMenu').style.display = 'none';
}
document.addEventListener('click', function(e) {
hideContextMenu();
});
function deleteSelectedMessage(event) {
if(event) event.stopPropagation();
hideContextMenu();
if (!selectedMsgId) return;
let msgElement = document.getElementById('msg-' + selectedMsgId);
let msgId = selectedMsgId;
if (selectedMsgSender === 'account') {
showDeleteModal('Delete Message?', [
{ text: 'Delete for Everyone', cls: 'del-red', action: function(){ closeDeleteModal(); executeDelete(msgId, msgElement, 'everyone'); } },
{ text: 'Delete for Me', cls: 'del-amber', action: function(){ closeDeleteModal(); executeDelete(msgId, msgElement, 'me'); } }
]);
} else {
showDeleteModal('Delete Message?', [
{ text: 'Delete for Me', cls: 'del-red', action: function(){ closeDeleteModal(); executeDelete(msgId, msgElement, 'me'); } }
]);
}
}
function executeDelete(id, msgElement, type) {
if (msgElement) msgElement.style.opacity = '0.5';
$.post('chat_api.php', { action: 'delete_message', id_chat: id, delete_type: type }, function(res) {
if(res.status === 'success') {
$('#msg-' + id).fadeOut(300, function() { $(this).remove(); });
} else {
if (msgElement) msgElement.style.opacity = '1';
Swal.fire('Failed', res.message || 'Message could not be deleted', 'error');
}
}, 'json');
}
// ========================
// BULK DELETE & SELECTION
// ========================
function enterSelectionMode(event) {
if(event) event.stopPropagation();
hideContextMenu();
isSelectionMode = true;
selectedMessages = [{ id: selectedMsgId, sender: selectedMsgSender }];
// Stop editing/replying if active
cancelEdit();
closeReplyBox();
// Switch header actions
document.getElementById('normalHeaderActions').style.display = 'none';
document.getElementById('selectionHeaderActions').style.display = 'flex';
document.getElementById('selectionCount').innerText = selectedMessages.length;
// Add selectable classes to all rows and selected to the first one
let rows = document.querySelectorAll('.msg-row');
rows.forEach(row => {
row.classList.add('selectable');
// Add onclick to rows that are currently loaded
row.setAttribute('onclick', `toggleSelectMessage(event, ${row.getAttribute('data-id')}, '${row.classList.contains('msg-right') ? 'account' : 'occupant'}')`);
});
document.getElementById('msg-' + selectedMsgId).classList.add('selected');
}
function exitSelectionMode() {
isSelectionMode = false;
selectedMessages = [];
// Switch header actions back
document.getElementById('normalHeaderActions').style.display = 'block';
document.getElementById('selectionHeaderActions').style.display = 'none';
// Remove selectable classes
let rows = document.querySelectorAll('.msg-row');
rows.forEach(row => {
row.classList.remove('selectable', 'selected');
row.removeAttribute('onclick'); // Important: remove the bulk select onclick
});
}
function toggleSelectMessage(event, idChat, sender) {
if(event) event.stopPropagation();
if(!isSelectionMode) return;
let index = selectedMessages.findIndex(m => m.id === idChat);
let row = document.getElementById('msg-' + idChat);
if (index > -1) {
// Deselect
selectedMessages.splice(index, 1);
if(row) row.classList.remove('selected');
} else {
// Select
selectedMessages.push({ id: idChat, sender: sender });
if(row) row.classList.add('selected');
}
document.getElementById('selectionCount').innerText = selectedMessages.length;
// If no messages selected, automatically exit selection mode
if (selectedMessages.length === 0) {
exitSelectionMode();
}
}
function triggerBulkDelete() {
if (selectedMessages.length === 0) return;
let onlyAdminMessages = selectedMessages.every(m => m.sender === 'account');
let count = selectedMessages.length;
if (onlyAdminMessages) {
showDeleteModal('Delete ' + count + ' Messages?', [
{ text: 'Delete for Everyone', cls: 'del-red', action: function(){ closeDeleteModal(); executeBulkDelete('everyone'); } },
{ text: 'Delete for Me', cls: 'del-amber', action: function(){ closeDeleteModal(); executeBulkDelete('me'); } }
]);
} else {
showDeleteModal('Delete ' + count + ' Messages?', [
{ text: 'Delete for Me', cls: 'del-red', action: function(){ closeDeleteModal(); executeBulkDelete('me'); } }
]);
}
}
function executeBulkDelete(type) {
let idsToDelete = selectedMessages.map(m => m.id);
// Dim the UI immediately
idsToDelete.forEach(id => {
let el = document.getElementById('msg-' + id);
if(el) el.style.opacity = '0.5';
});
$.post('chat_api.php', {
action: 'bulk_delete_messages',
id_chats: idsToDelete,
delete_type: type
}, function(res) {
if(res.status === 'success') {
idsToDelete.forEach(id => {
$('#msg-' + id).fadeOut(300, function() { $(this).remove(); });
});
exitSelectionMode();
} else {
// Revert opacity
idsToDelete.forEach(id => {
let el = document.getElementById('msg-' + id);
if(el) el.style.opacity = '1';
});
Swal.fire('Failed', res.message || 'Failed to delete some messages', 'error');
}
}, 'json');
}
function editSelectedMessage(event) {
if(event) event.stopPropagation();
hideContextMenu();
if (!selectedMsgId || selectedMsgSender !== 'account') return;
editingMsgId = selectedMsgId;
originalEditMsgText = selectedMsgText;
// Set UI to Edit Mode
document.getElementById('epText').innerText = originalEditMsgText;
document.getElementById('editPreview').style.display = 'block';
// If user was replying, cancel it so they don't do both
closeReplyBox();
let inputEl = document.getElementById('messageInput');
inputEl.value = originalEditMsgText;
inputEl.style.height = 'auto';
inputEl.style.height = (inputEl.scrollHeight) + 'px';
inputEl.focus();
}
function cancelEdit() {
editingMsgId = null;
originalEditMsgText = null;
document.getElementById('editPreview').style.display = 'none';
let inputEl = document.getElementById('messageInput');
inputEl.value = '';
inputEl.style.height = 'auto';
}
function saveEdit() {
let input = document.getElementById('messageInput');
let newText = input.value.trim();
if (!newText || !editingMsgId) return;
if (newText === originalEditMsgText) {
cancelEdit();
return;
}
let msgElement = document.getElementById('msg-' + editingMsgId);
if (msgElement) msgElement.style.opacity = '0.5';
let idToEdit = editingMsgId;
cancelEdit(); // Reset UI immediately
$.post('chat_api.php', { action: 'edit_message', id_chat: idToEdit, new_message: newText }, function(res) {
if (msgElement) msgElement.style.opacity = '1';
if (res.status === 'success') {
// Reload messages to ensure formatting consistency
if (activeOccupantId) {
$.post('chat_api.php', { action: 'get_messages', id_occupant: activeOccupantId }, function(res2) {
if(res2.status === 'success') {
renderMessages(res2.data);
}
}, 'json');
}
} else {
Swal.fire('Failed', res.message || 'Message could not be edited', 'error');
}
}, 'json');
}
// Profile and Dropdown Logic
document.addEventListener('click', function(e) {
let dropIcon = document.querySelector('#normalHeaderActions i');
let dropMenu = document.getElementById('headerDropdownMenu');
if (dropIcon && dropMenu && !dropIcon.contains(e.target) && !dropMenu.contains(e.target)) {
dropMenu.classList.remove('show-dropdown');
}
});
function toggleHeaderDropdown() {
document.getElementById('headerDropdownMenu').classList.toggle('show-dropdown');
}
function openProfileView() {
document.getElementById('headerDropdownMenu').classList.remove('show-dropdown');
if(activeOccupantData) {
document.getElementById('pvImage').src = activeOccupantData.foto || 'images/admin.png';
document.getElementById('pvName').innerText = activeOccupantData.name || '-';
document.getElementById('pvPhone').innerText = activeOccupantData.no_hp || '-';
document.getElementById('pvNik').innerText = activeOccupantData.nik_nip || '-';
}
document.getElementById('profileView').style.display = 'flex';
}
function closeProfileView() {
document.getElementById('profileView').style.display = 'none';
}
function deleteAllChats() {
document.getElementById('headerDropdownMenu').classList.remove('show-dropdown');
if(activeOccupantData && activeOccupantData.name) {
document.getElementById('deleteAll_occupant_name').innerText = activeOccupantData.name;
} else {
document.getElementById('deleteAll_occupant_name').innerText = "this occupant";
}
$('#modalDeleteAllChats').modal('show');
}
function executeDeleteAllChats(event) {
if (event) event.preventDefault();
$('#modalDeleteAllChats').modal('hide');
isSelectionMode = true;
selectedMessages = [];
let rows = document.querySelectorAll('.msg-row');
rows.forEach(row => {
let id = parseInt(row.getAttribute('data-id'));
let sender = row.classList.contains('msg-right') ? 'account' : 'occupant';
selectedMessages.push({id: id, sender: sender});
});
if (selectedMessages.length > 0) {
executeBulkDelete('me');
} else {
Swal.fire('Empty', 'No messages to delete', 'info');
isSelectionMode = false;
}
}
// Initial load
$(document).ready(function() {
const urlParams = new URLSearchParams(window.location.search);
const chatId = urlParams.get('id');
if (chatId) {
openChat(chatId);
} else {
loadContacts();
}
renderEmojis('smileys');
setInterval(function(){
if(isSelectionMode) return; // DON'T REFRESH HTML WHILE USER IS SELECTING
if(!document.getElementById('searchInput').value) {
loadContacts();
if(activeOccupantId) {
$.post('chat_api.php', { action: 'get_messages', id_occupant: activeOccupantId }, function(res2) {
if(res2.status === 'success') {
if(res2.occupant) {
document.getElementById('activeStatus').innerText = getOccupantStatusString(res2.occupant);
}
let hist = document.getElementById('chatHistory');
let newHtml = buildMessagesHtml(res2.data);
if(hist.innerHTML !== newHtml) {
let isScrolledBottom = hist.scrollHeight - hist.clientHeight <= hist.scrollTop + 10;
// Update innerHTML instead of appending
hist.innerHTML = newHtml;
if(isScrolledBottom) scrollToBottom();
}
}
}, 'json');
}
}
}, 5000);
});
</script>