445 lines
14 KiB
Vue
445 lines
14 KiB
Vue
<script setup>
|
|
import vuetifyInitialThemes from '@/plugins/vuetify/theme'
|
|
import ChatActiveChatUserProfileSidebarContent from '@/views/apps/chat/ChatActiveChatUserProfileSidebarContent.vue'
|
|
import ChatLeftSidebarContent from '@/views/apps/chat/ChatLeftSidebarContent.vue'
|
|
import ChatLog from '@/views/apps/chat/ChatLog.vue'
|
|
import ChatUserProfileSidebarContent from '@/views/apps/chat/ChatUserProfileSidebarContent.vue'
|
|
import { useChat } from '@/views/apps/chat/useChat'
|
|
import { useChatStore } from '@/views/apps/chat/useChatStore'
|
|
import { useResponsiveLeftSidebar } from '@core/composable/useResponsiveSidebar'
|
|
import { avatarText } from '@core/utils/formatters'
|
|
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
|
import { computed, nextTick, onMounted, ref, watch } from "vue"
|
|
import {
|
|
useDisplay,
|
|
useTheme,
|
|
} from 'vuetify'
|
|
import { useStore } from 'vuex'
|
|
const store1 = useStore()
|
|
const vuetifyDisplays = useDisplay()
|
|
const store = useChatStore()
|
|
const { isLeftSidebarOpen } = useResponsiveLeftSidebar(vuetifyDisplays.smAndDown)
|
|
const { resolveAvatarBadgeVariant } = useChat()
|
|
|
|
// Perfect scrollbar
|
|
const chatLogPS = ref(null)
|
|
|
|
const scrollToBottomInChatLog = () => {
|
|
const scrollEl = chatLogPS.value.$el || chatLogPS.value
|
|
|
|
scrollEl.scrollTop = scrollEl.scrollHeight
|
|
}
|
|
|
|
// Search query
|
|
const q = ref('')
|
|
|
|
watch(q, val => store.fetchChatsAndContacts(val), { immediate: true })
|
|
|
|
// Open Sidebar in smAndDown when "start conversation" is clicked
|
|
const startConversation = () => {
|
|
if (vuetifyDisplays.mdAndUp.value)
|
|
return
|
|
isLeftSidebarOpen.value = true
|
|
}
|
|
const chatLogContainer = ref(null)
|
|
|
|
onMounted(async () => {
|
|
await store1.dispatch("getPtaientForChat");
|
|
console.log('getPtaientForChat', store1.getters.getPatientChat)
|
|
nextTick(() => {
|
|
if (chatLogContainer.value) {
|
|
chatLogContainer.value.scrollTop = chatLogContainer.value.scrollHeight
|
|
}
|
|
})
|
|
})
|
|
|
|
// If you need to scroll to bottom after content changes:
|
|
const scrollToBottom = () => {
|
|
nextTick(() => {
|
|
if (chatLogContainer.value) {
|
|
chatLogContainer.value.scrollTop = chatLogContainer.value.scrollHeight
|
|
}
|
|
})
|
|
}
|
|
// Chat message
|
|
const msg = ref('')
|
|
|
|
const sendMessage = async () => {
|
|
if (!msg.value)
|
|
return
|
|
await store.sendMsg(msg.value)
|
|
|
|
// Reset message input
|
|
msg.value = ''
|
|
|
|
// Scroll to bottom
|
|
nextTick(() => {
|
|
scrollToBottom()
|
|
})
|
|
}
|
|
|
|
const openChatOfContact = async userId => {
|
|
await store.getChat(userId)
|
|
|
|
// Reset message input
|
|
msg.value = ''
|
|
|
|
// Set unseenMsgs to 0
|
|
const contact = store.chatsContacts.find(c => c.id === userId)
|
|
if (contact)
|
|
contact.chat.unseenMsgs = 0
|
|
|
|
// if smAndDown => Close Chat & Contacts left sidebar
|
|
if (vuetifyDisplays.smAndDown.value)
|
|
isLeftSidebarOpen.value = false
|
|
|
|
// Scroll to bottom
|
|
nextTick(() => {
|
|
scrollToBottom()
|
|
})
|
|
}
|
|
|
|
// User profile sidebar
|
|
const isUserProfileSidebarOpen = ref(false)
|
|
|
|
// Active chat user profile sidebar
|
|
const isActiveChatUserProfileSidebarOpen = ref(false)
|
|
|
|
// file input
|
|
const refInputEl = ref()
|
|
|
|
const moreList = [
|
|
{
|
|
title: 'View Contact',
|
|
value: 'View Contact',
|
|
},
|
|
{
|
|
title: 'Mute Notifications',
|
|
value: 'Mute Notifications',
|
|
},
|
|
{
|
|
title: 'Block Contact',
|
|
value: 'Block Contact',
|
|
},
|
|
{
|
|
title: 'Clear Chat',
|
|
value: 'Clear Chat',
|
|
},
|
|
{
|
|
title: 'Report',
|
|
value: 'Report',
|
|
},
|
|
]
|
|
|
|
const { name } = useTheme()
|
|
|
|
const chatContentContainerBg = computed(() => {
|
|
let color = 'transparent'
|
|
if (vuetifyInitialThemes)
|
|
color = vuetifyInitialThemes.themes?.[name.value].colors?.background
|
|
|
|
return color
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<VLayout class="chat-app-layout">
|
|
<!-- 👉 user profile sidebar -->
|
|
<VNavigationDrawer v-model="isUserProfileSidebarOpen" temporary touchless absolute class="user-profile-sidebar"
|
|
location="start" width="370">
|
|
<ChatUserProfileSidebarContent @close="isUserProfileSidebarOpen = false" />
|
|
</VNavigationDrawer>
|
|
|
|
<!-- 👉 Active Chat sidebar -->
|
|
<VNavigationDrawer v-model="isActiveChatUserProfileSidebarOpen" width="374" absolute temporary location="end"
|
|
touchless class="active-chat-user-profile-sidebar">
|
|
<ChatActiveChatUserProfileSidebarContent @close="isActiveChatUserProfileSidebarOpen = false" />
|
|
</VNavigationDrawer>
|
|
|
|
<!-- 👉 Left sidebar -->
|
|
<VNavigationDrawer v-model="isLeftSidebarOpen" absolute touchless location="start" width="370"
|
|
:temporary="$vuetify.display.smAndDown" class="chat-list-sidebar" :permanent="$vuetify.display.mdAndUp">
|
|
<ChatLeftSidebarContent v-model:isDrawerOpen="isLeftSidebarOpen" v-model:search="q"
|
|
@open-chat-of-contact="openChatOfContact" @show-user-profile="isUserProfileSidebarOpen = true"
|
|
@close="isLeftSidebarOpen = false" />
|
|
</VNavigationDrawer>
|
|
|
|
<!-- 👉 Chat content -->
|
|
<VMain class="chat-content-container">
|
|
<!-- 👉 Right content: Active Chat -->
|
|
<div v-if="store.activeChat" class="d-flex flex-column h-100">
|
|
<!-- 👉 Active chat header -->
|
|
<div class="active-chat-header d-flex align-center text-medium-emphasis bg-surface">
|
|
<!-- Sidebar toggler -->
|
|
<IconBtn class="d-md-none me-3" @click="isLeftSidebarOpen = true">
|
|
<VIcon icon="tabler-menu-2" />
|
|
</IconBtn>
|
|
|
|
<!-- avatar -->
|
|
<div class="d-flex align-center cursor-pointer" @click="isActiveChatUserProfileSidebarOpen = true">
|
|
<VBadge dot location="bottom right" offset-x="3" offset-y="0"
|
|
:color="resolveAvatarBadgeVariant(store.activeChat.contact.status)" bordered>
|
|
<VAvatar size="38" :variant="!store.activeChat.contact.avatar ? 'tonal' : undefined"
|
|
:color="!store.activeChat.contact.avatar ? resolveAvatarBadgeVariant(store.activeChat.contact.status) : undefined"
|
|
class="cursor-pointer">
|
|
<VImg v-if="store.activeChat.contact.avatar" :src="store.activeChat.contact.avatar"
|
|
:alt="store.activeChat.contact.fullName" />
|
|
<span v-else>{{ avatarText(store.activeChat.contact.fullName) }}</span>
|
|
</VAvatar>
|
|
</VBadge>
|
|
|
|
<div class="flex-grow-1 ms-4 overflow-hidden">
|
|
<p class="text-h6 mb-0 font-weight-regular">
|
|
{{ store.activeChat.contact.fullName }}
|
|
</p>
|
|
<p class="text-truncate mb-0 text-body-2">
|
|
{{ store.activeChat.contact.role }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<VSpacer />
|
|
|
|
<!-- Header right content -->
|
|
<div class="d-sm-flex align-center d-none">
|
|
<IconBtn>
|
|
<VIcon icon="tabler-phone-call" />
|
|
</IconBtn>
|
|
<IconBtn>
|
|
<VIcon icon="tabler-video" />
|
|
</IconBtn>
|
|
<IconBtn>
|
|
<VIcon icon="tabler-search" />
|
|
</IconBtn>
|
|
</div>
|
|
|
|
<MoreBtn :menu-list="moreList" density="comfortable" color="undefined" />
|
|
</div>
|
|
|
|
<VDivider />
|
|
|
|
<!-- Chat log -->
|
|
<div ref="chatLogContainer" class="chat-log-container">
|
|
<ChatLog />
|
|
</div>
|
|
|
|
<!-- Message form -->
|
|
<VForm class="chat-log-message-form mb-5 mx-5" @submit.prevent="sendMessage">
|
|
<VTextField :key="store.activeChat?.contact.id" v-model="msg" variant="solo"
|
|
class="chat-message-input" placeholder="Type your message..." density="default" autofocus>
|
|
<template #append-inner>
|
|
<IconBtn>
|
|
<VIcon icon="tabler-microphone" />
|
|
</IconBtn>
|
|
|
|
<IconBtn class="me-2" @click="refInputEl?.click()">
|
|
<VIcon icon="tabler-photo" />
|
|
</IconBtn>
|
|
|
|
<VBtn @click="sendMessage">
|
|
Send
|
|
</VBtn>
|
|
</template>
|
|
</VTextField>
|
|
|
|
<input ref="refInputEl" type="file" name="file" accept=".jpeg,.png,.jpg,GIF" hidden>
|
|
</VForm>
|
|
</div>
|
|
|
|
<!-- 👉 Start conversation -->
|
|
<div v-else class="d-flex h-100 align-center justify-center flex-column">
|
|
<VAvatar size="109" class="elevation-3 mb-6 bg-surface">
|
|
<VIcon size="50" class="rounded-0 text-high-emphasis" icon="tabler-message" />
|
|
</VAvatar>
|
|
<p class="mb-0 px-6 py-1 font-weight-medium text-lg elevation-3 rounded-xl text-high-emphasis bg-surface"
|
|
:class="[{ 'cursor-pointer': $vuetify.display.smAndDown }]" @click="startConversation">
|
|
Start Conversation
|
|
</p>
|
|
</div>
|
|
</VMain>
|
|
</VLayout>
|
|
</template>
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
@use "@styles/variables/_vuetify.scss";
|
|
@use "@core/scss/base/_mixins.scss";
|
|
@use "@layouts/styles/mixins" as layoutsMixins;
|
|
|
|
// Variables
|
|
$chat-app-header-height: 62px;
|
|
|
|
// Placeholders
|
|
%chat-header {
|
|
display: flex;
|
|
align-items: center;
|
|
min-block-size: $chat-app-header-height;
|
|
padding-inline: 1rem;
|
|
}
|
|
|
|
.chat-app-layout {
|
|
border-radius: vuetify.$card-border-radius;
|
|
|
|
@include mixins.elevation(vuetify.$card-elevation);
|
|
|
|
$sel-chat-app-layout: &;
|
|
|
|
@at-root {
|
|
.skin--bordered {
|
|
@include mixins.bordered-skin($sel-chat-app-layout);
|
|
}
|
|
}
|
|
|
|
.active-chat-user-profile-sidebar,
|
|
.user-profile-sidebar {
|
|
.v-navigation-drawer__content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
|
|
.chat-list-header,
|
|
.active-chat-header {
|
|
@extend %chat-header;
|
|
}
|
|
|
|
.chat-list-search {
|
|
.v-field__outline__start {
|
|
flex-basis: 20px !important;
|
|
border-radius: 28px 0 0 28px !important;
|
|
}
|
|
|
|
.v-field__outline__end {
|
|
border-radius: 0 28px 28px 0 !important;
|
|
}
|
|
|
|
@include layoutsMixins.rtl {
|
|
.v-field__outline__start {
|
|
flex-basis: 20px !important;
|
|
border-radius: 0 28px 28px 0 !important;
|
|
}
|
|
|
|
.v-field__outline__end {
|
|
border-radius: 28px 0 0 28px !important;
|
|
}
|
|
}
|
|
}
|
|
|
|
.chat-list-sidebar {
|
|
.v-navigation-drawer__content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
}
|
|
|
|
.chat-content-container {
|
|
/* stylelint-disable-next-line value-keyword-case */
|
|
background-color: v-bind(chatContentContainerBg);
|
|
min-height: 450px;
|
|
|
|
// Adjust the padding so text field height stays 48px
|
|
.chat-message-input {
|
|
.v-field__append-inner {
|
|
align-items: center;
|
|
padding-block-start: 0;
|
|
}
|
|
|
|
.v-field--appended {
|
|
padding-inline-end: 9px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.chat-user-profile-badge {
|
|
.v-badge__badge {
|
|
/* stylelint-disable liberty/use-logical-spec */
|
|
min-width: 12px !important;
|
|
height: 0.75rem;
|
|
/* stylelint-enable liberty/use-logical-spec */
|
|
}
|
|
}
|
|
|
|
.text-h6 {
|
|
font-size: .9375rem !important;
|
|
font-weight: 500;
|
|
line-height: 1.375rem;
|
|
letter-spacing: normal !important;
|
|
font-family: Public Sans, sans-serif, -apple-system, blinkmacsystemfont, Segoe UI, roboto, Helvetica Neue, arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol;
|
|
text-transform: none !important;
|
|
}
|
|
|
|
.text-body-2 {
|
|
font-size: .8125rem !important;
|
|
font-weight: 400;
|
|
line-height: 1.25rem;
|
|
letter-spacing: normal !important;
|
|
font-family: Public Sans, sans-serif, -apple-system, blinkmacsystemfont, Segoe UI, roboto, Helvetica Neue, arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", Segoe UI Symbol;
|
|
text-transform: none !important;
|
|
}
|
|
|
|
.chat-log-container {
|
|
height: 400px;
|
|
/* Adjust as needed */
|
|
overflow-y: auto;
|
|
padding-right: 15px;
|
|
scrollbar-width: none;
|
|
/* For Firefox */
|
|
-ms-overflow-style: none;
|
|
/* For Internet Explorer and Edge */
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
|
.chat-log-container::-webkit-scrollbar {
|
|
width: 0;
|
|
background: transparent;
|
|
}
|
|
|
|
/* Show scrollbar on hover */
|
|
.chat-log-container:hover {
|
|
scrollbar-width: thin;
|
|
/* For Firefox */
|
|
-ms-overflow-style: auto;
|
|
/* For Internet Explorer and Edge */
|
|
}
|
|
|
|
.chat-log-container:hover::-webkit-scrollbar {
|
|
width: 2px;
|
|
}
|
|
|
|
.chat-log-container:hover::-webkit-scrollbar-track {
|
|
background: rgba(0, 0, 0, 0.05);
|
|
border-radius: 10px;
|
|
}
|
|
|
|
.chat-log-container:hover::-webkit-scrollbar-thumb {
|
|
background-color: rgba(0, 0, 0, 0.2);
|
|
border-radius: 10px;
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
|
|
.chat-log-container:hover::-webkit-scrollbar-thumb:hover {
|
|
background-color: rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
/* Add a subtle shadow to indicate scrollable content */
|
|
.chat-log-container::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
width: 2px;
|
|
height: 100%;
|
|
background: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.05));
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.chat-log-container:hover::after {
|
|
opacity: 1;
|
|
}
|
|
</style>
|