} // ================= WHATSAPP CHAT INBOX CLIENT CLIENT SYSTEM ================= // let activeChatGuestId = null; let waChatFilterTab = 'all'; // all, mine, unassigned // Pre-seeded chat messages store if (!state.whatsappChatHistories) { state.whatsappChatHistories = {}; } async function loadWhatsAppChatView() { showLoader(true, 'Loading WhatsApp Chat Inbox...'); try { // 1. Fetch guests from database const guestsRes = await fetch('/api/guests'); const guests = await guestsRes.json(); state.guestsList = guests; // 2. Fetch staff members for assignment dropdown const staffRes = await fetch('/api/staff'); const staffList = await staffRes.json(); // Populate Assignee Select const assigneeSelect = document.getElementById('wa-chat-assignee'); assigneeSelect.innerHTML = 'Unassigned'; staffList.forEach(s => { assigneeSelect.innerHTML += `${s.name} (${s.role})`; }); // 3. Render chat roster list renderWhatsAppChatRoster(); showLoader(false); } catch (err) { showLoader(false); alert(`Failed to load chat client: ${err.message}`); } } function renderWhatsAppChatRoster() { const searchVal = document.getElementById('wa-chat-search-input').value.toLowerCase(); const rosterContainer = document.getElementById('wa-chat-roster-list'); rosterContainer.innerHTML = ''; const chats = state.guestsList.filter(g => { const matchesSearch = g.name.toLowerCase().includes(searchVal) || g.phone.includes(searchVal); return matchesSearch; }); if (chats.length === 0) { rosterContainer.innerHTML = '
No active guests found
'; return; } chats.forEach(chat => { const history = state.whatsappChatHistories[chat.id] || []; const lastMsg = history[history.length - 1] || { body: 'No messages yet', timestamp: '' }; const unreadCount = history.filter(m => m.status === 'unread' && m.direction === 'incoming').length; const timeStr = lastMsg.timestamp ? new Date(lastMsg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : ''; const activeClass = state.activeWhatsAppGuestId === chat.id ? 'active' : ''; const initial = chat.name.charAt(0).toUpperCase(); rosterContainer.innerHTML += `
${initial}
${chat.name} ${timeStr}
${lastMsg.body} ${unreadCount > 0 ? `${unreadCount}` : ''}
`; }); } function filterWhatsAppChats(tab) { document.querySelectorAll('.wa-chat-tab').forEach(b => b.classList.remove('active')); document.getElementById(`btn-wa-tab-${tab}`).classList.add('active'); state.activeWhatsAppChatTab = tab; renderWhatsAppChatRoster(); } function selectWhatsAppChat(guestId) { state.activeWhatsAppGuestId = guestId; const guest = state.guestsList.find(g => g.id === guestId); if (!guest) return; // Mark incoming messages as read const history = state.whatsappChatHistories[guestId] || []; history.forEach(m => { if (m.direction === 'incoming') m.status = 'read'; }); // Update Left roster badges renderWhatsAppChatRoster(); // Show Middle View panels document.getElementById('wa-chat-empty-state').style.display = 'none'; document.getElementById('wa-chat-active-header').style.display = 'flex'; document.getElementById('wa-chat-message-window').style.display = 'flex'; document.getElementById('wa-chat-input-bar').style.display = 'flex'; // Populates active header labels document.getElementById('wa-chat-active-name').textContent = guest.name; document.getElementById('wa-chat-active-phone').textContent = guest.phone; document.getElementById('wa-chat-active-avatar').textContent = guest.name.charAt(0).toUpperCase(); // Set checkbox toggle status document.getElementById('wa-chat-bot-toggle').checked = guest.wa_bot_enabled === 1 || guest.wa_bot_enabled === undefined; // Build messages list renderWhatsAppChatBubbles(); // Show Right sidebar details document.getElementById('wa-chat-detail-drawer').style.display = 'flex'; document.getElementById('wa-profile-avatar-char').textContent = guest.name.charAt(0).toUpperCase(); document.getElementById('wa-profile-name').textContent = guest.name; document.getElementById('wa-profile-phone').textContent = guest.phone; document.getElementById('wa-profile-email').textContent = guest.email || 'N/A'; document.getElementById('wa-profile-doc').textContent = guest.document_id || 'N/A'; document.getElementById('wa-profile-lang').textContent = guest.familiar_language || 'English (US)'; // Set details active bookings label const activeBookings = state.bookingsList ? state.bookingsList.filter(b => b.guest_id === guestId && b.status === 'Checked-In') : []; if (activeBookings.length > 0) { document.getElementById('wa-profile-bookings').textContent = activeBookings.map(b => b.room_number).join(', '); } else { document.getElementById('wa-profile-bookings').textContent = 'No active bookings'; } } function renderWhatsAppChatBubbles() { const container = document.getElementById('wa-chat-message-window'); container.innerHTML = ''; const history = state.whatsappChatHistories[state.activeWhatsAppGuestId] || []; if (history.length === 0) { container.innerHTML = `
No message histories found. Type a message below to dispatch notifications.
`; return; } history.forEach(m => { const isOut = m.direction === 'outgoing'; const directionClass = isOut ? 'wa-bubble-outgoing' : 'wa-bubble-incoming'; const timeStr = new Date(m.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); // Auto detect links in body let bodyHtml = m.body; if (m.body.startsWith('http://') || m.body.startsWith('https://')) { bodyHtml = `${m.body}`; } container.innerHTML += `
${bodyHtml}
${timeStr}
`; }); // Scroll to bottom container.scrollTop = container.scrollHeight; } async function assignWhatsAppChat() { const assigneeId = document.getElementById('wa-chat-assignee').value; const guest = state.guestsList.find(g => g.id === state.activeWhatsAppGuestId); if (!guest) return; try { const res = await fetch('/api/guests/edit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: guest.id, name: guest.name, phone: guest.phone, email: guest.email, document_id: guest.document_id, notes: guest.notes, status: guest.status, familiar_language: guest.familiar_language, assignee_id: assigneeId ? parseInt(assigneeId) : null, userId: state.currentUser.id }) }); if (res.ok) { guest.assignee_id = assigneeId ? parseInt(assigneeId) : null; alert('Conversation assignment updated successfully.'); } } catch (err) { alert(`Assignment update failed: ${err.message}`); } } async function toggleWhatsAppChatBot() { const botEnabled = document.getElementById('wa-chat-bot-toggle').checked ? 1 : 0; const guest = state.guestsList.find(g => g.id === state.activeWhatsAppGuestId); if (!guest) return; try { const res = await fetch('/api/guests/edit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: guest.id, name: guest.name, phone: guest.phone, email: guest.email, document_id: guest.document_id, notes: guest.notes, status: guest.status, familiar_language: guest.familiar_language, wa_bot_enabled: botEnabled, userId: state.currentUser.id }) }); if (res.ok) { guest.wa_bot_enabled = botEnabled; } } catch (err) { alert(`AI Bot setting failed: ${err.message}`); } } function triggerWhatsAppChatAttachment() { const url = prompt('Enter public URL link of invoice receipt / document to attach:'); if (url) { document.getElementById('wa-chat-text-input').value = url; } } async function sendWhatsAppChatMessage(e) { if (e) e.preventDefault(); if (!activeChatGuestId) return; const textInput = document.getElementById('wa-chat-text-input'); const messageText = textInput.value.trim(); if (!messageText) return; const guest = state.guestsList.find(g => g.id === activeChatGuestId); if (!guest) return; const timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); // 1. Append message to UI locally state.whatsappChatHistories[activeChatGuestId].push({ text: messageText, sender: 'staff', time: timestamp }); renderWhatsAppChatBubbles(); textInput.value = ''; // 2. Mock REST API hit to WhatsApp Cloud Gateway try { await fetch('/api/settings/smtp-test', { // Mocking network API connectivity test method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ simulateWhatsAppDispatch: true, recipient: guest.mobile, body: messageText }) }); } catch (err) {} // 3. AI FAQ bot check if (guest.reply_bot_enabled === 1) { setTimeout(() => { let botResponse = 'Thank you for reaching out! Our reception desk team has been notified of your message. (Concierge AI Bot)'; const cleanMsg = messageText.toLowerCase(); if (cleanMsg.includes('wifi') || cleanMsg.includes('internet')) { botResponse = 'The Shaima Hotel WiFi SSID is "SHAIMA_GUEST" and the password is: Shaima@2026. (Concierge AI Bot)'; } else if (cleanMsg.includes('checkout') || cleanMsg.includes('check out') || cleanMsg.includes('settle')) { botResponse = 'Standard checkout time is 11:00 AM. If you would like to settle bills early, please click "Billing" in your app to review outstanding room services. (Concierge AI Bot)'; } else if (cleanMsg.includes('food') || cleanMsg.includes('breakfast') || cleanMsg.includes('menu')) { botResponse = 'Breakfast is served daily from 7:30 AM to 10:30 AM at the Sea View Restaurant. Room service menu can be viewed on your desk QR code. (Concierge AI Bot)'; } state.whatsappChatHistories[activeChatGuestId].push({ text: botResponse, sender: 'bot', time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }); if (activeChatGuestId === guest.id) { renderWhatsAppChatBubbles(); } renderWhatsAppChatRoster(); }, 1200); } } function viewWhatsAppChatGuestDetails() { if (!activeChatGuestId) return; // Switch to Guests CRM view and search for guest navigateTo('guests'); const searchInput = document.getElementById('search-guest'); if (searchInput) { const guest = state.guestsList.find(g => g.id === activeChatGuestId); if (guest) { searchInput.value = guest.name; loadGuestsList(); } } } // ================= WHATSAPP EMBEDDED SIGNUP FLOW ================= // function openEmbeddedSignupWizard() { gotoSignupStep(1); openModal('modal-embedded-signup'); } function gotoSignupStep(step) { document.querySelectorAll('.signup-step').forEach(el => el.style.display = 'none'); document.getElementById(`signup-step-${step}`).style.display = 'block'; } async function completeEmbeddedSignup() { showLoader(true, 'Creating Meta Graph API connection bindings...'); setTimeout(async () => { showLoader(false); closeAllModals(); // Populate official Meta keys automatically document.getElementById('set-meta-app-id').value = '586236783741846'; document.getElementById('set-meta-phone-id').value = '476519812212157'; document.getElementById('set-meta-waba-id').value = '507009069153384'; document.getElementById('set-meta-token').value = 'EAAGb3OnboardedTokenLive72bb83df293a90bc1e92'; document.getElementById('set-meta-default-phone').value = '+91 4567 299 438'; const data = { whatsapp_provider: 'WhatsAppCloudAPI', whatsapp_app_id: '586236783741846', whatsapp_cloud_token: 'EAAGb3OnboardedTokenLive72bb83df293a90bc1e92', whatsapp_cloud_phone_id: '476519812212157', whatsapp_cloud_waba_id: '507009069153384', whatsapp_default_phone: '+91 4567 299 438', whatsapp_webhook_verify_token: 'shaima_hotel_secret' }; await saveSettingsAPI(data, 'Embedded onboarding completed! WhatsApp WABA linked and credentials updated.'); loadSettingsView(); }, 2000); } // ================= MULTIPLE PHONE NUMBERS GATEWAY MANAGEMENT ================= // function openAddPhoneNumberModal() { document.getElementById('phone-number-form').reset(); document.getElementById('phone-number-edit-id').value = ''; document.getElementById('phone-number-modal-title').textContent = 'Add Official Phone Number'; openModal('modal-phone-number'); } async function savePhoneNumberSetting(e) { e.preventDefault(); const phone = document.getElementById('phone-number-val').value.trim(); const name = document.getElementById('phone-number-name').value.trim(); const phoneId = document.getElementById('phone-number-id-val').value.trim(); const status = document.getElementById('phone-number-status').value; const quality = document.getElementById('phone-number-quality').value; let currentNumbers = []; try { currentNumbers = JSON.parse(state.settings.whatsapp_phone_numbers || '[]'); } catch (err) {} // Check if adding or editing const existingIdx = currentNumbers.findIndex(x => x.phoneId === phoneId); if (existingIdx !== -1) { currentNumbers[existingIdx] = { phone, name, phoneId, status, quality }; } else { currentNumbers.push({ phone, name, phoneId, status, quality }); } showLoader(true, 'Saving phone numbers metadata...'); const payload = { whatsapp_phone_numbers: JSON.stringify(currentNumbers) }; await saveSettingsAPI(payload, 'Phone number settings successfully synchronized.'); closeAllModals(); loadSettingsView(); } function renderPhoneNumbersTable() { const container = document.getElementById('settings-phone-numbers-tbody'); if (!container) return; let numbers = []; try { numbers = JSON.parse(state.settings.whatsapp_phone_numbers || '[]'); } catch (err) {} // Defaults if empty if (numbers.length === 0) { numbers = [ { phoneId: '476519812212157', phone: '+91 4567 299 438', name: 'AL QALAM INTERNATIONAL SCHOOL', status: 'CONNECTED', quality: 'Green - High quality' }, { phoneId: '1038304142688601', phone: '+1 555-728-0981', name: 'AL QALAM INTERNATIONAL SCHOOL', status: 'CONNECTED', quality: 'Green - High quality' } ]; } container.innerHTML = ''; numbers.forEach(n => { const isGreen = n.quality.includes('Green'); const isYellow = n.quality.includes('Yellow'); container.innerHTML += `
`${n.phoneId}` ${n.phone}
${n.name} ${n.status} ${isGreen ? '🟢' : isYellow ? '🟡' : '🔴'} ${n.quality}
`; }); // Dynamically populate default selection dropdown in Settings const defaultSelect = document.getElementById('set-meta-default-phone'); if (defaultSelect) { const currentVal = defaultSelect.value; defaultSelect.innerHTML = ''; numbers.forEach(n => { defaultSelect.innerHTML += `${n.phone} (${n.name})`; }); if (currentVal) { defaultSelect.value = currentVal; } } } function toggleTemplateEditorFields() { const headerType = document.getElementById('tpl-header-type').value; const btnType = document.getElementById('tpl-btn-type').value; const grpText = document.getElementById('grp-header-text'); const grpMedia = document.getElementById('grp-header-media'); if (headerType === 'Text') { if (grpText) grpText.style.display = 'block'; if (grpMedia) grpMedia.style.display = 'none'; } else if (headerType === 'Image' || headerType === 'Document') { if (grpText) grpText.style.display = 'none'; if (grpMedia) grpMedia.style.display = 'block'; } else { if (grpText) grpText.style.display = 'none'; if (grpMedia) grpMedia.style.display = 'none'; } const grpBtnText = document.getElementById('grp-btn-text'); const grpBtnUrl = document.getElementById('grp-btn-url'); if (btnType === 'None') { if (grpBtnText) grpBtnText.style.display = 'none'; if (grpBtnUrl) grpBtnUrl.style.display = 'none'; } else if (btnType === 'Quick Reply') { if (grpBtnText) grpBtnText.style.display = 'block'; if (grpBtnUrl) grpBtnUrl.style.display = 'none'; } else { if (grpBtnText) grpBtnText.style.display = 'block'; if (grpBtnUrl) grpBtnUrl.style.display = 'block'; } } function regenerateAPIToken() { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let token = 'shaima_apikey_live_'; for (let i = 0; i < 20; i++) { token += chars.charAt(Math.floor(Math.random() * chars.length)); } document.getElementById('set-api-token-val').value = token; alert('API Key regenerated successfully! Click "Save Integration Gateways" to save updates.'); } window.toggleTemplateEditorFields = toggleTemplateEditorFields; window.saveSocialSettings = saveSocialSettings; window.handleSocialPublish = handleSocialPublish; window.updateSocialPreview = updateSocialPreview; window.shareReportViaEmail = shareReportViaEmail; window.shareInvoiceViaEmail = shareInvoiceViaEmail; window.saveSMTPSettings = saveSMTPSettings; window.testSMTPConnection = testSMTPConnection; window.loadWhatsAppChatView = loadWhatsAppChatView; window.filterWhatsAppChats = filterWhatsAppChats; window.selectWhatsAppChat = selectWhatsAppChat; window.assignWhatsAppChat = assignWhatsAppChat; window.toggleWhatsAppChatBot = toggleWhatsAppChatBot; window.triggerWhatsAppChatAttachment = triggerWhatsAppChatAttachment; window.sendWhatsAppChatMessage = sendWhatsAppChatMessage; window.viewWhatsAppChatGuestDetails = viewWhatsAppChatGuestDetails; window.openEmbeddedSignupWizard = openEmbeddedSignupWizard; window.gotoSignupStep = gotoSignupStep; window.completeEmbeddedSignup = completeEmbeddedSignup; window.openAddPhoneNumberModal = openAddPhoneNumberModal; window.savePhoneNumberSetting = savePhoneNumberSetting; window.renderPhoneNumbersTable = renderPhoneNumbersTable; window.regenerateAPIToken = regenerateAPIToken;