diff --git a/hud/hud.js b/hud/hud.js
index 6ab56e3..93c7fd1 100644
--- a/hud/hud.js
+++ b/hud/hud.js
@@ -22,6 +22,12 @@ const HUD = {
isConnected: true,
businessName: '',
+ // Chat state
+ chatTaskId: null,
+ chatMessages: [],
+ chatPollInterval: null,
+ lastMessageId: 0,
+
// Category names (will be loaded from API)
categories: {
1: { name: 'Orders', color: '#ef4444' },
@@ -343,6 +349,11 @@ const HUD = {
this.tasks = this.tasks.filter(t => t.TaskID !== task.TaskID);
this.renderTasks();
this.showFeedback('Task accepted!', 'success');
+
+ // If it's a chat task, open the chat modal
+ if (this.isChatTask(task)) {
+ this.openChat(task);
+ }
} else {
this.showFeedback(data.ERROR || 'Failed to accept', 'error');
}
@@ -368,6 +379,190 @@ const HUD = {
} else {
indicator.classList.add('disconnected');
}
+ },
+
+ // Check if task is a chat task
+ isChatTask(task) {
+ if (!task) return false;
+ const name = (task.TaskTypeName || task.Title || '').toLowerCase();
+ return name.includes('chat');
+ },
+
+ // Open chat modal for a task
+ openChat(task) {
+ this.chatTaskId = task.TaskID;
+ this.chatMessages = [];
+ this.lastMessageId = 0;
+
+ document.getElementById('chatTitle').textContent = task.Title || 'Chat';
+ document.getElementById('chatMessages').innerHTML = '
Loading messages...
';
+ document.getElementById('chatOverlay').classList.add('visible');
+ document.getElementById('chatInput').focus();
+
+ // Load messages immediately and start polling
+ this.loadMessages();
+ this.chatPollInterval = setInterval(() => this.loadMessages(), 2000);
+ },
+
+ // Close chat modal
+ closeChat() {
+ document.getElementById('chatOverlay').classList.remove('visible');
+ if (this.chatPollInterval) {
+ clearInterval(this.chatPollInterval);
+ this.chatPollInterval = null;
+ }
+ this.chatTaskId = null;
+ this.chatMessages = [];
+ this.lastMessageId = 0;
+ },
+
+ // Load chat messages
+ async loadMessages() {
+ if (!this.chatTaskId) return;
+
+ try {
+ const response = await fetch('/api/chat/getMessages.cfm', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ TaskID: this.chatTaskId,
+ AfterMessageID: this.lastMessageId
+ })
+ });
+
+ const data = await response.json();
+
+ if (data.OK) {
+ const messages = data.MESSAGES || [];
+
+ // If first load, replace all; otherwise append new messages
+ if (this.lastMessageId === 0) {
+ this.chatMessages = messages;
+ } else if (messages.length > 0) {
+ this.chatMessages = [...this.chatMessages, ...messages];
+ }
+
+ // Update last message ID
+ if (messages.length > 0) {
+ this.lastMessageId = messages[messages.length - 1].MessageID;
+ }
+
+ this.renderMessages();
+
+ // Check if chat was closed
+ if (data.CHAT_CLOSED) {
+ this.showFeedback('Chat has ended', 'info');
+ this.closeChat();
+ }
+ }
+ } catch (err) {
+ console.error('[HUD] Load messages error:', err);
+ }
+ },
+
+ // Render chat messages
+ renderMessages() {
+ const container = document.getElementById('chatMessages');
+
+ if (this.chatMessages.length === 0) {
+ container.innerHTML = 'No messages yet. Start the conversation!
';
+ return;
+ }
+
+ container.innerHTML = this.chatMessages.map(msg => {
+ const isStaff = msg.SenderType === 'staff' || msg.SenderType === 'worker';
+ const time = new Date(msg.CreatedOn).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
+
+ return `
+
+
${msg.SenderName || (isStaff ? 'Staff' : 'Customer')}
+
${this.escapeHtml(msg.MessageBody)}
+
${time}
+
+ `;
+ }).join('');
+
+ // Scroll to bottom
+ container.scrollTop = container.scrollHeight;
+ },
+
+ // Send a chat message
+ async sendMessage() {
+ const input = document.getElementById('chatInput');
+ const message = input.value.trim();
+
+ if (!message || !this.chatTaskId) return;
+
+ input.value = '';
+ input.disabled = true;
+
+ try {
+ const response = await fetch('/api/chat/sendMessage.cfm', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ TaskID: this.chatTaskId,
+ Message: message,
+ SenderType: 'staff'
+ })
+ });
+
+ const data = await response.json();
+
+ if (data.OK) {
+ // Add message locally for immediate feedback
+ this.chatMessages.push({
+ MessageID: data.MessageID || Date.now(),
+ MessageBody: message,
+ SenderType: 'staff',
+ SenderName: 'Staff',
+ CreatedOn: new Date().toISOString()
+ });
+ this.renderMessages();
+ } else {
+ this.showFeedback('Failed to send message', 'error');
+ }
+ } catch (err) {
+ console.error('[HUD] Send message error:', err);
+ this.showFeedback('Failed to send message', 'error');
+ }
+
+ input.disabled = false;
+ input.focus();
+ },
+
+ // End the chat (complete the task)
+ async endChat() {
+ if (!this.chatTaskId) return;
+
+ if (!confirm('End this chat?')) return;
+
+ try {
+ const response = await fetch('/api/tasks/completeChat.cfm', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ TaskID: this.chatTaskId })
+ });
+
+ const data = await response.json();
+
+ if (data.OK) {
+ this.showFeedback('Chat ended', 'success');
+ this.closeChat();
+ } else {
+ this.showFeedback(data.ERROR || 'Failed to end chat', 'error');
+ }
+ } catch (err) {
+ console.error('[HUD] End chat error:', err);
+ this.showFeedback('Failed to end chat', 'error');
+ }
+ },
+
+ // Escape HTML for safe rendering
+ escapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
}
};
diff --git a/hud/index.html b/hud/index.html
index 255bbee..a9faa04 100644
--- a/hud/index.html
+++ b/hud/index.html
@@ -334,6 +334,157 @@
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
+
+ /* Chat modal */
+ .chat-overlay {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0,0,0,0.95);
+ z-index: 1100;
+ justify-content: center;
+ align-items: center;
+ padding: 20px;
+ }
+
+ .chat-overlay.visible {
+ display: flex;
+ }
+
+ .chat-container {
+ background: #1a1a1a;
+ border-radius: 12px;
+ width: 100%;
+ max-width: 500px;
+ height: 80vh;
+ max-height: 600px;
+ display: flex;
+ flex-direction: column;
+ border: 1px solid #333;
+ }
+
+ .chat-header {
+ padding: 16px;
+ border-bottom: 1px solid #333;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .chat-header h3 {
+ margin: 0;
+ color: #fff;
+ font-size: 18px;
+ }
+
+ .chat-close-btn {
+ background: #333;
+ border: none;
+ color: #fff;
+ padding: 8px 16px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ }
+
+ .chat-messages {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ }
+
+ .chat-message {
+ max-width: 80%;
+ padding: 10px 14px;
+ border-radius: 12px;
+ font-size: 14px;
+ line-height: 1.4;
+ }
+
+ .chat-message.customer {
+ align-self: flex-start;
+ background: #333;
+ color: #fff;
+ }
+
+ .chat-message.staff {
+ align-self: flex-end;
+ background: #2196f3;
+ color: #fff;
+ }
+
+ .chat-message .sender {
+ font-size: 11px;
+ opacity: 0.7;
+ margin-bottom: 4px;
+ }
+
+ .chat-message .time {
+ font-size: 10px;
+ opacity: 0.5;
+ margin-top: 4px;
+ text-align: right;
+ }
+
+ .chat-input-area {
+ padding: 16px;
+ border-top: 1px solid #333;
+ display: flex;
+ gap: 12px;
+ }
+
+ .chat-input {
+ flex: 1;
+ background: #333;
+ border: none;
+ border-radius: 8px;
+ padding: 12px 16px;
+ color: #fff;
+ font-size: 14px;
+ outline: none;
+ }
+
+ .chat-input::placeholder {
+ color: #666;
+ }
+
+ .chat-send-btn {
+ background: #22c55e;
+ border: none;
+ border-radius: 8px;
+ padding: 12px 20px;
+ color: #fff;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ }
+
+ .chat-send-btn:active {
+ transform: scale(0.98);
+ }
+
+ .chat-loading {
+ text-align: center;
+ color: #666;
+ padding: 20px;
+ }
+
+ .chat-end-btn {
+ background: #ef4444;
+ border: none;
+ color: #fff;
+ padding: 8px 16px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ margin-left: 8px;
+ }
@@ -385,6 +536,26 @@
+
+
+