From c9a3e00a9419832328ef51eb1133839d8a2f3cd6 Mon Sep 17 00:00:00 2001 From: Francisco Gaona Date: Thu, 8 Jan 2026 00:21:12 +0100 Subject: [PATCH] WIP - UI cahnges to bottom bar --- frontend/app.vue | 2 + frontend/components/AppSidebar.vue | 10 - frontend/components/BottomDrawer.vue | 365 ++++++++++++++++++++++++--- frontend/layouts/default.vue | 2 +- 4 files changed, 326 insertions(+), 53 deletions(-) diff --git a/frontend/app.vue b/frontend/app.vue index 7f8da09..a1afb46 100644 --- a/frontend/app.vue +++ b/frontend/app.vue @@ -1,11 +1,13 @@ diff --git a/frontend/components/AppSidebar.vue b/frontend/components/AppSidebar.vue index 4e67370..7a8774d 100644 --- a/frontend/components/AppSidebar.vue +++ b/frontend/components/AppSidebar.vue @@ -342,16 +342,6 @@ const centralAdminMenuItems: Array<{ - - - - Softphone - - - diff --git a/frontend/components/BottomDrawer.vue b/frontend/components/BottomDrawer.vue index 5d264f1..5c74e0c 100644 --- a/frontend/components/BottomDrawer.vue +++ b/frontend/components/BottomDrawer.vue @@ -2,25 +2,29 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Button } from '@/components/ui/button' -import { Switch } from '@/components/ui/switch' -import { Label } from '@/components/ui/label' +import { Input } from '@/components/ui/input' import AIChatBar from '@/components/AIChatBar.vue' -import { Phone, Sparkles, X } from 'lucide-vue-next' +import { Phone, Sparkles, X, ChevronUp, Hash, Mic, MicOff, PhoneIncoming, PhoneOff } from 'lucide-vue-next' +import { toast } from 'vue-sonner' +import { useSoftphone } from '~/composables/useSoftphone' const isDrawerOpen = useState('bottomDrawerOpen', () => false) const activeTab = useState('bottomDrawerTab', () => 'softphone') +const drawerHeight = useState('bottomDrawerHeight', () => 240) -const drawerHeight = ref(240) -const minHeight = 160 +const softphone = useSoftphone() + +const minHeight = 200 +const collapsedHeight = 72 const maxHeight = ref(480) const isResizing = ref(false) const resizeStartY = ref(0) const resizeStartHeight = ref(0) -const softphoneEnabled = ref(false) -const softphoneConnected = ref(false) +const phoneNumber = ref('') +const showDialpad = ref(false) -const statusLabel = computed(() => (softphoneConnected.value ? 'Connected' : 'Offline')) +const statusLabel = computed(() => (softphone.isConnected.value ? 'Connected' : 'Offline')) const clampHeight = (height: number) => Math.min(Math.max(height, minHeight), maxHeight.value) @@ -30,7 +34,24 @@ const updateMaxHeight = () => { drawerHeight.value = clampHeight(drawerHeight.value) } +const openDrawer = (tab?: string) => { + if (tab) { + activeTab.value = tab + } + isDrawerOpen.value = true + if (activeTab.value === 'softphone') { + softphone.open() + } +} + +const minimizeDrawer = () => { + isDrawerOpen.value = false +} + const startResize = (event: MouseEvent | TouchEvent) => { + if (!isDrawerOpen.value) { + isDrawerOpen.value = true + } isResizing.value = true resizeStartY.value = 'touches' in event ? event.touches[0].clientY : event.clientY resizeStartHeight.value = drawerHeight.value @@ -47,19 +68,109 @@ const stopResize = () => { isResizing.value = false } -const closeDrawer = () => { - isDrawerOpen.value = false -} +watch( + () => softphone.incomingCall.value, + (incoming) => { + if (incoming) { + activeTab.value = 'softphone' + isDrawerOpen.value = true + } + } +) -watch(softphoneEnabled, (enabled) => { - if (!enabled) { - softphoneConnected.value = false +watch( + () => activeTab.value, + (tab) => { + if (tab === 'softphone' && isDrawerOpen.value) { + softphone.open() + } + } +) + +watch( + () => isDrawerOpen.value, + (open) => { + if (open && activeTab.value === 'softphone') { + softphone.open() + } + } +) + +const handleCall = async () => { + if (!phoneNumber.value) { + toast.error('Please enter a phone number') return } - softphoneConnected.value = true -}) + + try { + await softphone.initiateCall(phoneNumber.value) + phoneNumber.value = '' + toast.success('Call initiated') + } catch (error: any) { + toast.error(error.message || 'Failed to initiate call') + } +} + +const handleAccept = async () => { + if (!softphone.incomingCall.value) return + + try { + await softphone.acceptCall(softphone.incomingCall.value.callSid) + } catch (error: any) { + toast.error(error.message || 'Failed to accept call') + } +} + +const handleReject = async () => { + if (!softphone.incomingCall.value) return + + try { + await softphone.rejectCall(softphone.incomingCall.value.callSid) + } catch (error: any) { + toast.error(error.message || 'Failed to reject call') + } +} + +const handleEndCall = async () => { + if (!softphone.currentCall.value) return + + try { + await softphone.endCall(softphone.currentCall.value.callSid) + } catch (error: any) { + toast.error(error.message || 'Failed to end call') + } +} + +const handleDtmf = async (digit: string) => { + if (!softphone.currentCall.value) return + + try { + await softphone.sendDtmf(softphone.currentCall.value.callSid, digit) + } catch (error: any) { + console.error('Failed to send DTMF:', error) + } +} + +const formatPhoneNumber = (number: string): string => { + if (!number) return '' + const cleaned = number.replace(/\D/g, '') + if (cleaned.length === 11 && cleaned[0] === '1') { + return `+1 (${cleaned.slice(1, 4)}) ${cleaned.slice(4, 7)}-${cleaned.slice(7)}` + } else if (cleaned.length === 10) { + return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6)}` + } + return number +} + +const formatDuration = (seconds?: number): string => { + if (!seconds) return '--:--' + const mins = Math.floor(seconds / 60) + const secs = seconds % 60 + return `${mins}:${secs.toString().padStart(2, '0')}` +} onMounted(() => { + console.log('BottomDrawer mounted'); updateMaxHeight() window.addEventListener('mousemove', handleResize) window.addEventListener('mouseup', stopResize) @@ -69,6 +180,7 @@ onMounted(() => { }) onBeforeUnmount(() => { + console.log('BottomDrawer unmounted'); window.removeEventListener('mousemove', handleResize) window.removeEventListener('mouseup', stopResize) window.removeEventListener('touchmove', handleResize) @@ -78,13 +190,30 @@ onBeforeUnmount(() => {