diff --git a/app/controllers/api/v1/accounts/voice_controller.rb b/app/controllers/api/v1/accounts/voice_controller.rb index 93cd804f0..0b61d893d 100644 --- a/app/controllers/api/v1/accounts/voice_controller.rb +++ b/app/controllers/api/v1/accounts/voice_controller.rb @@ -51,29 +51,17 @@ class Api::V1::Accounts::VoiceController < Api::V1::Accounts::BaseController # Update call status using the unified CallStatusManager # The CallStatusManager will determine if the call is outbound internally + # and will create an appropriate activity message + custom_message = "Call ended by #{current_user.name}" + status_manager = Voice::CallStatus::Manager.new( conversation: @conversation, call_sid: call_sid, provider: :twilio ) - status_manager.process_status_update('completed') - - # CallStatusManager handles all voice call message updates - - # Create an activity message noting the call has ended - Messages::MessageBuilder.new( - nil, - @conversation, - { - content: 'Call ended by agent', - message_type: :activity, - additional_attributes: { - call_sid: call_sid, - call_status: 'completed', - ended_by: current_user.name - } - } - ).perform + status_manager.process_status_update('completed', nil, false, custom_message) + + # No need to create additional activity messages - the manager handles it # Broadcast call status update on the account channel ActionCable.server.broadcast( @@ -143,33 +131,19 @@ class Api::V1::Accounts::VoiceController < Api::V1::Accounts::BaseController name: current_user.name } - # Update call status using the unified CallStatusManager + # Update call status using the unified CallStatusManager with custom message # The CallStatusManager will determine if the call is outbound internally + custom_message = "#{current_user.name} joined the call" + status_manager = Voice::CallStatus::Manager.new( conversation: @conversation, call_sid: call_sid, provider: :twilio ) - status_manager.process_status_update('in-progress') + status_manager.process_status_update('in-progress', nil, false, custom_message) # Save the conversation with agent join details @conversation.save! - - # Create an activity message - Messages::MessageBuilder.new( - nil, - @conversation, - { - content: "#{current_user.name} joined the call", - message_type: :activity, - additional_attributes: { - call_sid: call_sid, - conference_sid: conference_sid, - joined_by: current_user.name, - joined_at: Time.now.to_i - } - } - ).perform # Broadcast call status update on the account channel ActionCable.server.broadcast( @@ -220,20 +194,19 @@ class Api::V1::Accounts::VoiceController < Api::V1::Accounts::BaseController } @conversation.save! - # Create an activity message noting the agent rejected the call - Messages::MessageBuilder.new( - nil, - @conversation, - { - content: "#{current_user.name} declined to answer", - message_type: :activity, - additional_attributes: { - call_sid: call_sid, - rejected_by: current_user.name, - rejected_at: Time.now.to_i - } - } - ).perform + # Update call status and create activity message through the unified manager + custom_message = "#{current_user.name} declined to answer" + + status_manager = Voice::CallStatus::Manager.new( + conversation: @conversation, + call_sid: call_sid, + provider: :twilio + ) + status_manager.create_activity_message(custom_message, { + call_sid: call_sid, + rejected_by: current_user.name, + rejected_at: Time.now.to_i + }) render json: { status: 'success', diff --git a/app/javascript/dashboard/components/widgets/FloatingCallWidget.vue b/app/javascript/dashboard/components/widgets/FloatingCallWidget.vue index 8afe2d856..2bedaad16 100644 --- a/app/javascript/dashboard/components/widgets/FloatingCallWidget.vue +++ b/app/javascript/dashboard/components/widgets/FloatingCallWidget.vue @@ -1546,8 +1546,65 @@ export default { { immediate: true } ); - // This function was removed as it's no longer needed - + // Add watcher for call status changes + watch( + () => store.state.calls.activeCall?.status, + (newStatus) => { + if (newStatus === 'ended' || newStatus === 'completed' || newStatus === 'missed') { + console.log('Call status changed to:', newStatus); + stopDurationTimer(); + stopRingtone(); + isCallActive.value = false; + + // Update app state + if (window.app && window.app.$data) { + window.app.$data.showCallWidget = false; + } + + // Emit event + emit('callEnded'); + + // Clear store state + store.dispatch('calls/clearActiveCall'); + store.dispatch('calls/clearIncomingCall'); + } + } + ); + + // Add specific watcher for outbound call status + watch( + () => store.state.calls.activeCall, + (newCall) => { + // Check if this is an outbound call + const isOutboundCall = newCall && newCall.isOutbound === true; + + if (isOutboundCall) { + console.log('Outbound call status:', newCall?.status); + + // Handle outbound call status changes + if (newCall?.status === 'ended' || newCall?.status === 'completed' || newCall?.status === 'missed') { + console.log('Outbound call ended with status:', newCall.status); + stopDurationTimer(); + stopRingtone(); + isCallActive.value = false; + + // Update app state + if (window.app && window.app.$data) { + window.app.$data.showCallWidget = false; + } + + // Emit event + emit('callEnded'); + + // Clear store state + store.dispatch('calls/clearActiveCall'); + store.dispatch('calls/clearIncomingCall'); + } + } + }, + { deep: true } // Watch for nested changes in the call object + ); + return { isCallActive, callDuration, diff --git a/app/javascript/dashboard/helper/actionCable.js b/app/javascript/dashboard/helper/actionCable.js index 5ecbb5925..dcc1d4e65 100644 --- a/app/javascript/dashboard/helper/actionCable.js +++ b/app/javascript/dashboard/helper/actionCable.js @@ -232,13 +232,40 @@ class ActionCableConnector extends BaseActionCableConnector { status: data.status, conversationId: data.conversation_id, inboxId: data.inbox_id, + timestamp: data.timestamp || Date.now() }; // Update store with call status change this.app.$store.dispatch('calls/handleCallStatusChanged', normalizedPayload); - // For non-terminal statuses, update the active call - if (!['ended', 'missed', 'completed'].includes(data.status)) { + // For terminal statuses, clear the active call to close the widget + if (['ended', 'missed', 'completed', 'failed', 'busy', 'no_answer'].includes(data.status)) { + console.log(`ActionCable: Call status changed to terminal status: ${data.status}`); + + // Clear active call for terminal statuses + this.app.$store.dispatch('calls/clearActiveCall'); + + // Ensure window.app.$data exists before modifying it + if (window.app && window.app.$data) { + console.log('ActionCable: Hiding call widget'); + window.app.$data.showCallWidget = false; + } + + // Update conversation list to show current status + if (data.conversation_id) { + console.log(`ActionCable: Updating conversation last activity for conversation ${data.conversation_id}`); + this.app.$store.dispatch('updateConversationLastActivity', { + conversationId: data.conversation_id, + lastActivityAt: new Date().toISOString(), + }); + + // Also ensure that the conversation gets refreshed + this.app.$store.dispatch('fetchConversation', { + id: data.conversation_id + }); + } + } else { + // Update active call for non-terminal statuses this.app.$store.dispatch('calls/setActiveCall', normalizedPayload); } }; diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/CallManager.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/CallManager.vue index d5c7c4eee..d01f55ddf 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/contact/CallManager.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/CallManager.vue @@ -29,7 +29,8 @@ export default { const transcription = ref(''); const durationTimer = ref(null); const isCallActive = computed( - () => callStatus.value && callStatus.value !== 'completed' + () => callStatus.value && + !['completed', 'ended', 'missed', 'failed', 'busy', 'no-answer'].includes(callStatus.value) ); const callStatusText = computed(() => { @@ -77,16 +78,24 @@ export default { const updateCallStatus = status => { callStatus.value = status; - if (status === 'in-progress') { + // Log the status update to help with debugging + console.log(`CallManager: Updating call status to ${status}`); + + if (status === 'in-progress' || status === 'in_progress') { startDurationTimer(); } else if ( - status === 'completed' || - status === 'failed' || - status === 'busy' || - status === 'no-answer' + ['completed', 'ended', 'missed', 'failed', 'busy', 'no-answer', 'no_answer'].includes(status) ) { + console.log(`CallManager: Call ended with status ${status}`); stopDurationTimer(); emit('callEnded'); + + // Forcefully hide the call widget after a short delay + setTimeout(() => { + if (window.app && window.app.$data) { + window.app.$data.showCallWidget = false; + } + }, 2000); } }; @@ -271,8 +280,8 @@ export default {