chore: Update feedback fixes
This commit is contained in:
@@ -6,6 +6,10 @@ import LabelItem from 'dashboard/components-next/label/LabelItem.vue';
|
||||
import AddLabel from 'dashboard/components-next/label/AddLabel.vue';
|
||||
|
||||
const props = defineProps({
|
||||
wrapperRef: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
contactId: {
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
@@ -117,10 +121,7 @@ const handleLabelHover = labelId => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-2 ltr:mr-10 rtl:ml-10"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
<div class="flex flex-wrap items-center gap-2" @mouseleave="handleMouseLeave">
|
||||
<LabelItem
|
||||
v-for="label in savedLabels"
|
||||
:key="label.id"
|
||||
@@ -132,6 +133,7 @@ const handleLabelHover = labelId => {
|
||||
/>
|
||||
<AddLabel
|
||||
:label-menu-items="labelMenuItems"
|
||||
:wrapper-ref="wrapperRef"
|
||||
@update-label="handleLabelAction"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -126,7 +126,7 @@ const closeMobileSidebar = () => {
|
||||
<!-- Desktop sidebar -->
|
||||
<div
|
||||
v-if="slots.sidebar"
|
||||
class="hidden lg:block overflow-y-auto justify-end min-w-52 w-full py-5 max-w-md border-l border-n-weak bg-n-surface-2"
|
||||
class="hidden lg:block overflow-y-auto justify-end min-w-52 w-full py-5 max-w-md ltr:border-l rtl:border-r border-n-weak bg-n-surface-2"
|
||||
>
|
||||
<slot name="sidebar" />
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,8 @@ const emit = defineEmits(['goToContactsList']);
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
|
||||
const wrapperRef = ref(null);
|
||||
|
||||
const confirmDeleteContactDialogRef = ref(null);
|
||||
|
||||
const avatarFile = ref(null);
|
||||
@@ -123,7 +125,7 @@ const handleAvatarDelete = async () => {
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-start gap-6 pb-6 w-full">
|
||||
<div class="flex flex-col items-start gap-3 w-full">
|
||||
<div ref="wrapperRef" class="flex flex-col items-start gap-3 w-full">
|
||||
<Avatar
|
||||
:src="avatarSrc || ''"
|
||||
:name="selectedContact?.name || ''"
|
||||
@@ -173,7 +175,10 @@ const handleAvatarDelete = async () => {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ContactLabels :contact-id="selectedContact?.id" />
|
||||
<ContactLabels
|
||||
:wrapper-ref="wrapperRef"
|
||||
:contact-id="selectedContact?.id"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-start gap-6 w-full">
|
||||
<ContactsForm
|
||||
|
||||
+3
-3
@@ -116,7 +116,7 @@ const labelsCountText = computed(() => {
|
||||
});
|
||||
}
|
||||
if (!showAllLabels.value && hiddenLabelsCount.value > 0) {
|
||||
return `+${hiddenLabelsCount.value}`;
|
||||
return hiddenLabelsCount.value;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
@@ -181,8 +181,8 @@ const onShowLabels = e => {
|
||||
xs
|
||||
slate
|
||||
:no-animation="disableToggle"
|
||||
:icon="labelsCountText ? '' : 'i-lucide-chevron-left'"
|
||||
class="!py-0 !px-1.5 flex-shrink-0 !rounded-md !bg-n-button-color -outline-offset-1"
|
||||
:icon="labelsCountText ? 'i-lucide-plus' : 'i-lucide-chevron-left'"
|
||||
class="!py-0 !px-1.5 flex-shrink-0 !rounded-md !bg-n-button-color -outline-offset-1 !gap-0.5 [&>span:first-child]:!text-n-slate-10 [&>span:last-child]:!text-n-slate-11"
|
||||
:class="{ 'cursor-default': disableToggle }"
|
||||
@click="onShowLabels"
|
||||
/>
|
||||
|
||||
-5
@@ -48,11 +48,6 @@ const showSection = computed(() => {
|
||||
>
|
||||
<div class="flex-1 min-w-0 flex items-center gap-0.5">
|
||||
<InboxName v-if="showInboxName" :inbox="inbox" class="min-w-0" />
|
||||
<div
|
||||
v-if="showInboxName && hasSlaPolicyId"
|
||||
class="rounded-lg h-3 w-px bg-n-strong mx-1"
|
||||
/>
|
||||
<SLACardLabel v-if="hasSlaPolicyId" ref="slaCardLabel" :chat="chat" />
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 flex-shrink-0">
|
||||
<CardPriorityIcon
|
||||
|
||||
+16
-2
@@ -1,11 +1,12 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, useTemplateRef } from 'vue';
|
||||
import { getLastMessage } from 'dashboard/helper/conversationHelper';
|
||||
import CardMetaSection from './CardMetaSection.vue';
|
||||
import CardAvatar from './CardAvatar.vue';
|
||||
import CardHeader from './CardHeader.vue';
|
||||
import CardContent from './CardContent.vue';
|
||||
import CardLabels from './CardLabels.vue';
|
||||
import SLACardLabel from 'dashboard/components-next/Conversation/Sla/SLACardLabel.vue';
|
||||
|
||||
const props = defineProps({
|
||||
chat: { type: Object, required: true },
|
||||
@@ -24,6 +25,8 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['selectConversation', 'click', 'contextmenu']);
|
||||
|
||||
const slaCardLabel = useTemplateRef('slaCardLabel');
|
||||
|
||||
const lastMessageInChat = computed(() => getLastMessage(props.chat));
|
||||
const showLabelsSection = computed(() => props.chat.labels?.length > 0);
|
||||
const showExpandedPreview = computed(
|
||||
@@ -37,6 +40,10 @@ const voiceCallData = computed(() => ({
|
||||
|
||||
const unreadCount = computed(() => props.chat?.unread_count);
|
||||
|
||||
const hasSlaPolicyId = computed(
|
||||
() => props.chat?.sla_policy_id || slaCardLabel.value?.hasSlaThreshold
|
||||
);
|
||||
|
||||
const onSelectConversation = checked => {
|
||||
emit('selectConversation', checked);
|
||||
};
|
||||
@@ -103,8 +110,9 @@ const onSelectConversation = checked => {
|
||||
v-if="showLabelsSection || isInboxView"
|
||||
:labels="chat.labels"
|
||||
>
|
||||
<template v-if="isInboxView" #before>
|
||||
<template #before>
|
||||
<CardMetaSection
|
||||
v-if="isInboxView"
|
||||
:chat="chat"
|
||||
:inbox="inbox"
|
||||
:show-assignee="showAssignee"
|
||||
@@ -112,6 +120,12 @@ const onSelectConversation = checked => {
|
||||
:is-labels-empty="chat.labels.length === 0"
|
||||
inline
|
||||
/>
|
||||
<SLACardLabel
|
||||
v-else-if="hasSlaPolicyId"
|
||||
ref="slaCardLabel"
|
||||
data-before-slot
|
||||
:chat="chat"
|
||||
/>
|
||||
</template>
|
||||
</CardLabels>
|
||||
</div>
|
||||
|
||||
+16
-4
@@ -116,11 +116,23 @@ const selectedModel = computed({
|
||||
<InboxName v-if="showInboxName" :inbox="inbox" class="min-w-0" />
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="text-label-small outline outline-1 -outline-offset-1 outline-n-weak px-1.5 rounded-md h-6 text-n-slate-11 min-w-12 flex-shrink-0 inline-flex items-center justify-center truncate"
|
||||
<div class="w-px h-3 bg-n-slate-6 flex-shrink-0" />
|
||||
|
||||
<div
|
||||
v-tooltip.top="{
|
||||
content: chat.id,
|
||||
delay: { show: 500, hide: 0 },
|
||||
}"
|
||||
class="h-6 flex items-center gap-1 max-w-20 w-full min-w-0"
|
||||
>
|
||||
{{ chat.id }}
|
||||
</span>
|
||||
<Icon
|
||||
icon="i-woot-hash"
|
||||
class="size-3.5 text-n-slate-10 flex-shrink-0"
|
||||
/>
|
||||
<span class="text-label-small text-n-slate-11 truncate">
|
||||
{{ chat.id }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<CardAvatar
|
||||
:contact="currentContact"
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useCamelCase } from 'dashboard/composables/useTransformKeys';
|
||||
|
||||
import SLAPopoverCard from './SLAPopoverCard.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import Label from 'dashboard/components-next/label/Label.vue';
|
||||
|
||||
const props = defineProps({
|
||||
chat: {
|
||||
@@ -35,9 +36,6 @@ const appliedSLA = computed(() => props.chat?.applied_sla);
|
||||
const slaEvents = computed(() => props.chat?.sla_events);
|
||||
const hasSlaThreshold = computed(() => slaStatus.value?.threshold);
|
||||
const isSlaMissed = computed(() => slaStatus.value?.isSlaMissed);
|
||||
const slaTextStyles = computed(() =>
|
||||
isSlaMissed.value ? 'text-n-ruby-11' : 'text-n-amber-11'
|
||||
);
|
||||
|
||||
const conversation = computed(() => useCamelCase(props.chat, { deep: true }));
|
||||
|
||||
@@ -83,16 +81,15 @@ defineExpose({
|
||||
v-bind="$attrs"
|
||||
class="relative flex items-center cursor-pointer min-w-fit group"
|
||||
>
|
||||
<div class="flex items-center w-full truncate gap-1" :class="slaTextStyles">
|
||||
<Icon
|
||||
icon="i-lucide-flame"
|
||||
class="flex-shrink-0 size-3"
|
||||
:class="slaTextStyles"
|
||||
/>
|
||||
<span class="text-label-small" :class="slaTextStyles">
|
||||
{{ slaStatus.threshold }}
|
||||
</span>
|
||||
</div>
|
||||
<Label
|
||||
:label="slaStatus.threshold"
|
||||
:color="isSlaMissed ? 'ruby' : 'amber'"
|
||||
compact
|
||||
>
|
||||
<template #icon>
|
||||
<Icon icon="i-lucide-flame" class="flex-shrink-0 size-3.5" />
|
||||
</template>
|
||||
</Label>
|
||||
<SLAPopoverCard
|
||||
v-if="showSlaPopoverCard"
|
||||
:sla-missed-events="slaEvents"
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useDropdownPosition } from 'dashboard/composables/useDropdownPosition';
|
||||
import { useResizeObserver } from '@vueuse/core';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
labelMenuItems: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
wrapperRef: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['updateLabel']);
|
||||
@@ -22,15 +28,54 @@ const showDropdown = ref(false);
|
||||
const triggerRef = ref(null);
|
||||
const dropdownRef = ref(null);
|
||||
|
||||
const { positionClasses } = useDropdownPosition(
|
||||
const { position, updatePosition } = useDropdownPosition(
|
||||
triggerRef,
|
||||
dropdownRef,
|
||||
showDropdown
|
||||
showDropdown,
|
||||
{ container: props.wrapperRef }
|
||||
);
|
||||
|
||||
const openDropdown = async () => {
|
||||
showDropdown.value = !showDropdown.value;
|
||||
if (showDropdown.value) {
|
||||
await nextTick();
|
||||
updatePosition();
|
||||
}
|
||||
};
|
||||
|
||||
const updateLabel = label => {
|
||||
emit('updateLabel', label);
|
||||
};
|
||||
|
||||
const closeDropdown = () => {
|
||||
showDropdown.value = false;
|
||||
};
|
||||
|
||||
// Watch labelMenuItems and update position when they change
|
||||
watch(
|
||||
() => props.labelMenuItems,
|
||||
async () => {
|
||||
if (showDropdown.value) {
|
||||
await nextTick();
|
||||
updatePosition();
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// Watch wrapper container for size changes (hover effects from parent)
|
||||
useResizeObserver(props.wrapperRef, () => {
|
||||
if (showDropdown.value) {
|
||||
updatePosition();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div
|
||||
v-on-click-outside="[closeDropdown, { ignore: [triggerRef] }]"
|
||||
class="relative"
|
||||
>
|
||||
<Button
|
||||
ref="triggerRef"
|
||||
:label="t('LABEL.TAG_BUTTON')"
|
||||
@@ -39,17 +84,17 @@ const { positionClasses } = useDropdownPosition(
|
||||
:variant="showDropdown ? 'faded' : 'solid'"
|
||||
class="font-460 !-outline-offset-1"
|
||||
icon="i-lucide-plus"
|
||||
@click="showDropdown = !showDropdown"
|
||||
@click="openDropdown()"
|
||||
/>
|
||||
<DropdownMenu
|
||||
v-if="showDropdown"
|
||||
ref="dropdownRef"
|
||||
v-on-clickaway="() => (showDropdown = false)"
|
||||
:menu-items="labelMenuItems"
|
||||
show-search
|
||||
class="z-[100] w-48 overflow-y-auto max-h-52"
|
||||
:class="positionClasses"
|
||||
@action="emit('updateLabel', $event)"
|
||||
:class="position.class"
|
||||
:style="position.style"
|
||||
@action="updateLabel($event)"
|
||||
>
|
||||
<template #thumbnail="{ item }">
|
||||
<div
|
||||
|
||||
@@ -47,10 +47,10 @@ const colorClasses = computed(() => COLOR_CLASSES[props.color]);
|
||||
<template>
|
||||
<div
|
||||
:title="labelDescription"
|
||||
class="gap-1.5 rounded-lg -outline-offset-1 outline outline-1 inline-flex items-center flex-shrink-0"
|
||||
class="rounded-lg -outline-offset-1 outline outline-1 inline-flex items-center flex-shrink-0"
|
||||
:class="[
|
||||
colorClasses,
|
||||
compact ? 'px-1.5 h-6 gap-1 rounded-md' : 'px-2.5 h-8 rounded-lg',
|
||||
compact ? 'px-1.5 h-6 gap-1 rounded-md' : 'px-2.5 h-8 gap-1.5 rounded-lg',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
|
||||
Reference in New Issue
Block a user