mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-10-03 12:10:28 +00:00
Merge pull request #375 from abhinavxd/feat/self-assign-nudge-on-reply
feat: nudge agent to self-assign after replying to an unassigned conversation
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<Transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="opacity-0 translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-in duration-150"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-2"
|
||||
>
|
||||
<div v-if="show" class="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 w-[calc(100%-2rem)] max-w-md">
|
||||
<div class="flex items-center gap-3 rounded-lg border border-border bg-background px-3 py-2 shadow-lg">
|
||||
<span class="flex-1 text-sm text-foreground">{{ $t('conversation.assignSelfPrompt') }}</span>
|
||||
<Button size="sm" @click="$emit('assign')">{{ $t('conversation.assignSelfAction') }}</Button>
|
||||
<button
|
||||
type="button"
|
||||
class="text-muted-foreground hover:text-foreground"
|
||||
:aria-label="$t('globals.messages.close')"
|
||||
@click="$emit('dismiss')"
|
||||
>
|
||||
<X class="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Button } from '@shared-ui/components/ui/button'
|
||||
import { X } from 'lucide-vue-next'
|
||||
|
||||
defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
defineEmits(['assign', 'dismiss'])
|
||||
</script>
|
||||
@@ -67,6 +67,13 @@
|
||||
:unread-count="unReadMessages"
|
||||
@scroll-to-bottom="handleScrollToBottom"
|
||||
/>
|
||||
|
||||
<!-- Nudge to self-assign after replying to an unassigned conversation -->
|
||||
<AssignSelfNudge
|
||||
:show="showAssignNudge"
|
||||
@assign="assignToSelf"
|
||||
@dismiss="showAssignNudge = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -80,6 +87,7 @@ import { useUserStore } from '@main/stores/user'
|
||||
import { Button } from '@shared-ui/components/ui/button'
|
||||
import { RefreshCw, Loader2 } from 'lucide-vue-next'
|
||||
import ScrollToBottomButton from '@shared-ui/components/ScrollToBottomButton'
|
||||
import AssignSelfNudge from './AssignSelfNudge.vue'
|
||||
import { useEmitter } from '@main/composables/useEmitter'
|
||||
import { EMITTER_EVENTS } from '@main/constants/emitterEvents'
|
||||
import MessagesSkeleton from './MessagesSkeleton.vue'
|
||||
@@ -99,9 +107,14 @@ const threadEl = ref(null)
|
||||
const contentEl = ref(null)
|
||||
const emitter = useEmitter()
|
||||
const unReadMessages = ref(0)
|
||||
const showAssignNudge = ref(false)
|
||||
let currentConversationUUID = ''
|
||||
let openScrollDone = false
|
||||
|
||||
const assignToSelf = () => {
|
||||
conversationStore.updateAssignee('user', { assignee_id: userStore.userID })
|
||||
}
|
||||
|
||||
const { hasUserScrolled, scrollToBottom, scrollToOffset, handleScroll } = useStickyScroll(threadEl, contentEl, {
|
||||
onArriveBottom: () => { unReadMessages.value = 0 }
|
||||
})
|
||||
@@ -141,8 +154,12 @@ const applyOpenScroll = () => {
|
||||
|
||||
const newMessageHandler = (data) => {
|
||||
if (data.conversation_uuid !== conversationStore.current.uuid) return
|
||||
if (data.message?.sender_id === userStore.userID) {
|
||||
const message = data.message
|
||||
if (message?.sender_id === userStore.userID) {
|
||||
hasUserScrolled.value = false
|
||||
if (message.type === 'outgoing' && !message.private && !conversationStore.current.assigned_user_id) {
|
||||
showAssignNudge.value = true
|
||||
}
|
||||
return
|
||||
}
|
||||
if (hasUserScrolled.value) unReadMessages.value++
|
||||
@@ -163,6 +180,14 @@ watch(
|
||||
currentConversationUUID = newUUID
|
||||
unReadMessages.value = 0
|
||||
openScrollDone = false
|
||||
showAssignNudge.value = false
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => conversationStore.current?.assigned_user_id,
|
||||
(assignedUserId) => {
|
||||
if (assignedUserId) showAssignNudge.value = false
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -743,6 +743,7 @@ export const useConversationStore = defineStore('conversation', () => {
|
||||
async function updateAssignee (type, v) {
|
||||
try {
|
||||
await api.updateAssignee(conversation.data.uuid, type, v)
|
||||
conversation.data[`assigned_${type}_id`] = v.assignee_id
|
||||
} catch (error) {
|
||||
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
|
||||
variant: 'destructive',
|
||||
|
||||
@@ -478,6 +478,8 @@
|
||||
"contextLink.urlTemplateHelp": "{'{{token}}'} is a base64-encoded AES-256-GCM encrypted blob containing all contact and agent fields (requires secret). Individual variables like {'{{email}}'}, {'{{phone}}'}, {'{{external_user_id}}'}, {'{{contact_id}}'}, {'{{first_name}}'}, {'{{last_name}}'}, {'{{conversation_uuid}}'} are passed as plain text.",
|
||||
"conversation.agentAssigned": "Agent assigned",
|
||||
"conversation.allLoaded": "All conversations loaded",
|
||||
"conversation.assignSelfAction": "Assign to me",
|
||||
"conversation.assignSelfPrompt": "This conversation isn't assigned to anyone.",
|
||||
"conversation.bulkActions.clearSelection": "Clear selection",
|
||||
"conversation.bulkActions.failedToast": "Some conversations couldn't be updated",
|
||||
"conversation.bulkActions.selectAll": "Select all conversations",
|
||||
|
||||
Reference in New Issue
Block a user