feat(v4): Update Inbox view card design (#10599)
This commit is contained in:
@@ -23,16 +23,16 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="text-center bg-slate-25 dark:bg-slate-800 justify-center w-full h-full hidden md:flex items-center"
|
||||
class="items-center justify-center hidden w-full h-full text-center bg-n-background lg:flex"
|
||||
>
|
||||
<span v-if="uiFlags.isFetching" class="spinner my-4" />
|
||||
<span v-if="uiFlags.isFetching" class="my-4 spinner" />
|
||||
<div v-else class="flex flex-col items-center gap-2">
|
||||
<fluent-icon
|
||||
icon="mail-inbox"
|
||||
size="40"
|
||||
class="text-slate-600 dark:text-slate-400"
|
||||
/>
|
||||
<span class="text-slate-500 text-sm font-medium dark:text-slate-300">
|
||||
<span class="text-sm font-medium text-slate-500 dark:text-slate-300">
|
||||
{{ emptyMessage }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
|
||||
import InboxCard from './components/InboxCard.vue';
|
||||
import InboxCard from 'dashboard/components-next/Inbox/InboxCard.vue';
|
||||
import InboxListHeader from './components/InboxListHeader.vue';
|
||||
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import IntersectionObserver from 'dashboard/components/IntersectionObserver.vue';
|
||||
@@ -43,6 +43,8 @@ export default {
|
||||
meta: 'notifications/getMeta',
|
||||
uiFlags: 'notifications/getUIFlags',
|
||||
notification: 'notifications/getFilteredNotifications',
|
||||
notificationV4: 'notifications/getFilteredNotificationsV4',
|
||||
inboxById: 'inboxes/getInboxById',
|
||||
}),
|
||||
currentNotificationId() {
|
||||
return Number(this.$route.params.notification_id);
|
||||
@@ -58,6 +60,9 @@ export default {
|
||||
notifications() {
|
||||
return this.notification(this.inboxFilters);
|
||||
},
|
||||
notificationsV4() {
|
||||
return this.notificationV4(this.inboxFilters);
|
||||
},
|
||||
showEndOfList() {
|
||||
return this.uiFlags.isAllNotificationsLoaded && !this.uiFlags.isFetching;
|
||||
},
|
||||
@@ -77,6 +82,9 @@ export default {
|
||||
this.fetchNotifications();
|
||||
},
|
||||
methods: {
|
||||
stateInbox(inboxId) {
|
||||
return this.inboxById(inboxId);
|
||||
},
|
||||
fetchNotifications() {
|
||||
this.page = 1;
|
||||
this.$store.dispatch('notifications/clear');
|
||||
@@ -164,15 +172,42 @@ export default {
|
||||
this.inboxFilters
|
||||
);
|
||||
},
|
||||
openConversation(notification) {
|
||||
const {
|
||||
id,
|
||||
primaryActorId,
|
||||
primaryActorType,
|
||||
primaryActor: { inboxId },
|
||||
notificationType,
|
||||
} = notification;
|
||||
|
||||
if (this.$route.params.notification_id !== id) {
|
||||
useTrack(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, {
|
||||
notificationType,
|
||||
});
|
||||
|
||||
this.$store.dispatch('notifications/read', {
|
||||
id,
|
||||
primaryActorId,
|
||||
primaryActorType,
|
||||
unreadCount: this.meta.unreadCount,
|
||||
});
|
||||
|
||||
this.$router.push({
|
||||
name: 'inbox_view_conversation',
|
||||
params: { inboxId, notification_id: id },
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="flex w-full h-full bg-white dark:bg-slate-900">
|
||||
<section class="flex w-full h-full bg-n-solid-1">
|
||||
<div
|
||||
class="flex flex-col h-full w-full md:min-w-[360px] md:max-w-[360px] ltr:border-r border-slate-50 dark:border-slate-800/50"
|
||||
:class="!currentNotificationId ? 'flex' : 'hidden md:flex'"
|
||||
class="flex flex-col h-full w-full lg:min-w-[400px] lg:max-w-[400px] ltr:border-r border-slate-50 dark:border-slate-800/50"
|
||||
:class="!currentNotificationId ? 'flex' : 'hidden xl:flex'"
|
||||
>
|
||||
<InboxListHeader
|
||||
:is-context-menu-open="isInboxContextMenuOpen"
|
||||
@@ -181,18 +216,25 @@ export default {
|
||||
/>
|
||||
<div
|
||||
ref="notificationList"
|
||||
class="flex flex-col w-full h-[calc(100%-56px)] overflow-x-hidden overflow-y-auto"
|
||||
class="flex flex-col gap-px w-full h-[calc(100%-56px)] pb-3 overflow-x-hidden px-3 overflow-y-auto divide-y divide-n-strong [&>*:hover]:!border-y-transparent [&>*.active]:!border-y-transparent [&>*:hover+*]:!border-t-transparent [&>*.active+*]:!border-t-transparent"
|
||||
>
|
||||
<InboxCard
|
||||
v-for="notificationItem in notifications"
|
||||
v-for="notificationItem in notificationsV4"
|
||||
:key="notificationItem.id"
|
||||
:active="currentNotificationId === notificationItem.id"
|
||||
:notification-item="notificationItem"
|
||||
:inbox-item="notificationItem"
|
||||
:state-inbox="stateInbox(notificationItem.primaryActor?.inboxId)"
|
||||
class="rounded-none hover:rounded-xl hover:bg-n-alpha-1 dark:hover:bg-n-alpha-3"
|
||||
:class="
|
||||
currentNotificationId === notificationItem.id
|
||||
? 'bg-n-alpha-1 dark:bg-n-alpha-3 click-animation rounded-xl active'
|
||||
: ''
|
||||
"
|
||||
@mark-notification-as-read="markNotificationAsRead"
|
||||
@mark-notification-as-un-read="markNotificationAsUnRead"
|
||||
@delete-notification="deleteNotification"
|
||||
@context-menu-open="isInboxContextMenuOpen = true"
|
||||
@context-menu-close="isInboxContextMenuOpen = false"
|
||||
@click="openConversation(notificationItem)"
|
||||
/>
|
||||
<div v-if="uiFlags.isFetching" class="text-center">
|
||||
<span class="mt-4 mb-4 spinner" />
|
||||
@@ -214,3 +256,23 @@ export default {
|
||||
<CmdBarConversationSnooze />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.click-animation {
|
||||
animation: click-animation 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes click-animation {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -175,7 +175,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full w-full md:w-[calc(100%-360px)]">
|
||||
<div class="h-full w-full xl:w-[calc(100%-400px)]">
|
||||
<div v-if="showEmptyState" class="flex w-full h-full">
|
||||
<InboxEmptyState
|
||||
:empty-state-message="$t('INBOX.LIST.NO_MESSAGES_AVAILABLE')"
|
||||
@@ -198,7 +198,7 @@ export default {
|
||||
</div>
|
||||
<ConversationBox
|
||||
v-else
|
||||
class="h-[calc(100%-56px)]"
|
||||
class="h-[calc(100%-56px)] [&.conversation-details-wrap]:!border-0"
|
||||
is-inbox-view
|
||||
:inbox-id="inboxId"
|
||||
:is-contact-panel-open="isContactPanelOpen"
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
<script>
|
||||
import PriorityIcon from './PriorityIcon.vue';
|
||||
import StatusIcon from './StatusIcon.vue';
|
||||
import InboxNameAndId from './InboxNameAndId.vue';
|
||||
import InboxContextMenu from './InboxContextMenu.vue';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import { dynamicTime, shortTimestamp } from 'shared/helpers/timeHelper';
|
||||
import { snoozedReopenTime } from 'dashboard/helper/snoozeHelpers';
|
||||
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PriorityIcon,
|
||||
InboxContextMenu,
|
||||
StatusIcon,
|
||||
InboxNameAndId,
|
||||
Thumbnail,
|
||||
},
|
||||
props: {
|
||||
notificationItem: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'contextMenuClose',
|
||||
'contextMenuOpen',
|
||||
'markNotificationAsRead',
|
||||
'markNotificationAsUnRead',
|
||||
'deleteNotification',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
isContextMenuOpen: false,
|
||||
contextMenuPosition: { x: null, y: null },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
primaryActor() {
|
||||
return this.notificationItem?.primary_actor;
|
||||
},
|
||||
inbox() {
|
||||
return this.$store.getters['inboxes/getInbox'](
|
||||
this.primaryActor.inbox_id
|
||||
);
|
||||
},
|
||||
isUnread() {
|
||||
return !this.notificationItem?.read_at;
|
||||
},
|
||||
meta() {
|
||||
return this.primaryActor?.meta;
|
||||
},
|
||||
assigneeMeta() {
|
||||
return this.meta?.assignee;
|
||||
},
|
||||
pushTitle() {
|
||||
return this.$t(
|
||||
`INBOX.TYPES.${this.notificationItem.notification_type.toUpperCase()}`
|
||||
);
|
||||
},
|
||||
lastActivityAt() {
|
||||
const time = dynamicTime(this.notificationItem?.last_activity_at);
|
||||
return shortTimestamp(time, true);
|
||||
},
|
||||
menuItems() {
|
||||
const items = [
|
||||
{
|
||||
key: 'delete',
|
||||
label: this.$t('INBOX.MENU_ITEM.DELETE'),
|
||||
},
|
||||
];
|
||||
|
||||
if (!this.isUnread) {
|
||||
items.push({
|
||||
key: 'mark_as_unread',
|
||||
label: this.$t('INBOX.MENU_ITEM.MARK_AS_UNREAD'),
|
||||
});
|
||||
} else {
|
||||
items.push({
|
||||
key: 'mark_as_read',
|
||||
label: this.$t('INBOX.MENU_ITEM.MARK_AS_READ'),
|
||||
});
|
||||
}
|
||||
return items;
|
||||
},
|
||||
snoozedUntilTime() {
|
||||
const { snoozed_until: snoozedUntil } = this.notificationItem;
|
||||
return snoozedUntil;
|
||||
},
|
||||
snoozedDisplayText() {
|
||||
if (this.snoozedUntilTime) {
|
||||
return `${this.$t('INBOX.LIST.SNOOZED_UNTIL')} ${snoozedReopenTime(
|
||||
this.snoozedUntilTime
|
||||
)}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
unmounted() {
|
||||
this.closeContextMenu();
|
||||
},
|
||||
methods: {
|
||||
openConversation(notification) {
|
||||
const {
|
||||
id,
|
||||
primary_actor_id: primaryActorId,
|
||||
primary_actor_type: primaryActorType,
|
||||
primary_actor: { inbox_id: inboxId },
|
||||
notification_type: notificationType,
|
||||
} = notification;
|
||||
|
||||
if (this.$route.params.notification_id !== id) {
|
||||
useTrack(INBOX_EVENTS.OPEN_CONVERSATION_VIA_INBOX, {
|
||||
notificationType,
|
||||
});
|
||||
|
||||
this.$store.dispatch('notifications/read', {
|
||||
id,
|
||||
primaryActorId,
|
||||
primaryActorType,
|
||||
unreadCount: this.meta.unreadCount,
|
||||
});
|
||||
|
||||
this.$router.push({
|
||||
name: 'inbox_view_conversation',
|
||||
params: { inboxId, notification_id: id },
|
||||
});
|
||||
}
|
||||
},
|
||||
closeContextMenu() {
|
||||
this.isContextMenuOpen = false;
|
||||
this.contextMenuPosition = { x: null, y: null };
|
||||
this.$emit('contextMenuClose');
|
||||
},
|
||||
openContextMenu(e) {
|
||||
this.closeContextMenu();
|
||||
e.preventDefault();
|
||||
this.contextMenuPosition = {
|
||||
x: e.pageX || e.clientX,
|
||||
y: e.pageY || e.clientY,
|
||||
};
|
||||
this.isContextMenuOpen = true;
|
||||
this.$emit('contextMenuOpen');
|
||||
},
|
||||
handleAction(key) {
|
||||
switch (key) {
|
||||
case 'mark_as_read':
|
||||
this.$emit('markNotificationAsRead', this.notificationItem);
|
||||
break;
|
||||
case 'mark_as_unread':
|
||||
this.$emit('markNotificationAsUnRead', this.notificationItem);
|
||||
break;
|
||||
case 'delete':
|
||||
this.$emit('deleteNotification', this.notificationItem);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
role="button"
|
||||
class="flex flex-col ltr:pl-5 rtl:pl-3 rtl:pr-5 ltr:pr-3 gap-2.5 py-3 w-full border-b border-slate-50 dark:border-slate-800/50 hover:bg-slate-25 dark:hover:bg-slate-800 cursor-pointer"
|
||||
:class="
|
||||
active
|
||||
? 'bg-slate-25 dark:bg-slate-800 click-animation'
|
||||
: 'bg-white dark:bg-slate-900'
|
||||
"
|
||||
@contextmenu="openContextMenu($event)"
|
||||
@click="openConversation(notificationItem)"
|
||||
>
|
||||
<div class="relative flex items-center justify-between w-full">
|
||||
<div
|
||||
v-if="isUnread"
|
||||
class="absolute ltr:-left-3.5 rtl:-right-3.5 flex w-2 h-2 rounded bg-woot-500 dark:bg-woot-500"
|
||||
/>
|
||||
<InboxNameAndId :inbox="inbox" :conversation-id="primaryActor.id" />
|
||||
|
||||
<div class="flex gap-2">
|
||||
<PriorityIcon :priority="primaryActor.priority" />
|
||||
<StatusIcon :status="primaryActor.status" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center justify-between w-full gap-2">
|
||||
<Thumbnail
|
||||
v-if="assigneeMeta"
|
||||
:src="assigneeMeta.thumbnail"
|
||||
:username="assigneeMeta.name"
|
||||
size="16px"
|
||||
/>
|
||||
<span
|
||||
class="flex-1 overflow-hidden text-sm text-slate-800 dark:text-slate-50 text-ellipsis whitespace-nowrap"
|
||||
:class="isUnread ? 'font-medium' : 'font-normal'"
|
||||
>
|
||||
{{ pushTitle }}
|
||||
</span>
|
||||
<span
|
||||
class="text-xs font-medium text-slate-600 dark:text-slate-300 whitespace-nowrap"
|
||||
>
|
||||
{{ lastActivityAt }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="snoozedUntilTime" class="flex items-center">
|
||||
<span class="text-xs font-medium text-woot-500 dark:text-woot-500">
|
||||
{{ snoozedDisplayText }}
|
||||
</span>
|
||||
</div>
|
||||
<InboxContextMenu
|
||||
v-if="isContextMenuOpen"
|
||||
:context-menu-position="contextMenuPosition"
|
||||
:menu-items="menuItems"
|
||||
@close="closeContextMenu"
|
||||
@select-action="handleAction"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.click-animation {
|
||||
animation: click-animation 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes click-animation {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -37,7 +37,7 @@ export default {
|
||||
@close="handleClose"
|
||||
>
|
||||
<div
|
||||
class="bg-white dark:bg-slate-900 w-40 py-1 border shadow-md border-slate-100 dark:border-slate-700/50 rounded-xl"
|
||||
class="bg-n-alpha-3 backdrop-blur-[100px] w-40 py-2 px-2 outline outline-1 outline-n-container shadow-lg rounded-xl"
|
||||
>
|
||||
<MenuItem
|
||||
v-for="item in menuItems"
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NextButton,
|
||||
},
|
||||
emits: ['filter'],
|
||||
|
||||
setup() {
|
||||
@@ -110,77 +115,63 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col bg-white z-50 dark:bg-slate-900 w-[170px] border shadow-md border-slate-100 dark:border-slate-700/50 rounded-xl divide-y divide-slate-100 dark:divide-slate-700/50"
|
||||
class="flex flex-col bg-n-alpha-3 backdrop-blur-[100px] border-0 outline outline-1 outline-n-container shadow-lg z-50 w-[170px] rounded-xl divide-y divide-n-weak dark:divide-n-strong"
|
||||
>
|
||||
<div class="flex items-center justify-between p-3 rounded-t-lg h-11">
|
||||
<div class="flex gap-1.5">
|
||||
<fluent-icon
|
||||
icon="arrow-sort"
|
||||
type="outline"
|
||||
size="16"
|
||||
class="text-slate-700 dark:text-slate-100"
|
||||
/>
|
||||
<span class="text-xs font-medium text-slate-800 dark:text-slate-100">
|
||||
<span class="i-lucide-arrow-down-up size-3.5 text-n-slate-12" />
|
||||
<span class="text-xs font-medium text-n-slate-12">
|
||||
{{ $t('INBOX.DISPLAY_MENU.SORT') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<div
|
||||
role="button"
|
||||
class="border h-5 flex gap-1 rounded-md items-center pr-1.5 pl-1 py-0.5 w-[70px] justify-between border-slate-100 dark:border-slate-700/50"
|
||||
<div v-on-clickaway="() => (showSortMenu = false)" class="relative">
|
||||
<NextButton
|
||||
:label="activeSortOption"
|
||||
icon="i-lucide-chevron-down"
|
||||
slate
|
||||
trailing-icon
|
||||
xs
|
||||
outline
|
||||
class="w-20"
|
||||
@click="openSortMenu"
|
||||
>
|
||||
<span class="text-xs font-medium text-slate-600 dark:text-slate-300">
|
||||
{{ activeSortOption }}
|
||||
</span>
|
||||
<fluent-icon
|
||||
icon="chevron-down"
|
||||
size="12"
|
||||
class="text-slate-600 dark:text-slate-200"
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
<div
|
||||
v-if="showSortMenu"
|
||||
class="absolute flex flex-col gap-0.5 bg-white z-60 dark:bg-slate-800 rounded-md p-0.5 top-0 w-[70px] border border-slate-100 dark:border-slate-700/50"
|
||||
class="absolute flex flex-col gap-0.5 bg-n-alpha-3 backdrop-blur-[100px] z-60 rounded-lg p-0.5 w-20 top-px outline outline-1 outline-n-container dark:outline-n-strong"
|
||||
>
|
||||
<div
|
||||
v-for="option in sortOptions"
|
||||
:key="option.key"
|
||||
role="button"
|
||||
class="flex rounded-[4px] h-5 w-full items-center justify-between p-0.5 gap-1"
|
||||
class="flex rounded-md h-5 w-full items-center justify-between px-1.5 py-0.5 gap-1"
|
||||
:class="{
|
||||
'bg-woot-50 dark:bg-woot-700/50': activeSort === option.key,
|
||||
'bg-n-brand/10 dark:bg-n-brand/10': activeSort === option.key,
|
||||
}"
|
||||
@click.stop="onSortOptionClick(option)"
|
||||
>
|
||||
<span
|
||||
class="text-xs font-medium hover:text-woot-600 dark:hover:text-woot-600"
|
||||
class="text-xs font-medium hover:text-n-brand dark:hover:text-n-brand"
|
||||
:class="{
|
||||
'text-woot-600 dark:text-woot-600': activeSort === option.key,
|
||||
'text-slate-600 dark:text-slate-300': activeSort !== option.key,
|
||||
'text-n-blue-text dark:text-n-blue-text':
|
||||
activeSort === option.key,
|
||||
'text-n-slate-11': activeSort !== option.key,
|
||||
}"
|
||||
>
|
||||
{{ option.name }}
|
||||
</span>
|
||||
<fluent-icon
|
||||
<span
|
||||
v-if="activeSort === option.key"
|
||||
icon="checkmark"
|
||||
size="14"
|
||||
class="text-woot-600 dark:text-woot-600"
|
||||
class="i-lucide-check size-2.5 text-n-blue-text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
class="px-3 py-4 text-xs font-medium text-slate-400 dark:text-slate-400"
|
||||
>
|
||||
<span class="px-3 py-4 text-xs font-medium text-n-slate-11">
|
||||
{{ $t('INBOX.DISPLAY_MENU.DISPLAY') }}
|
||||
</span>
|
||||
<div
|
||||
class="flex flex-col divide-y divide-slate-100 dark:divide-slate-700/50"
|
||||
>
|
||||
<div class="flex flex-col divide-y divide-n-weak dark:divide-n-strong">
|
||||
<div
|
||||
v-for="option in displayOptions"
|
||||
:key="option.key"
|
||||
@@ -191,7 +182,7 @@ export default {
|
||||
type="checkbox"
|
||||
:name="option.key"
|
||||
:checked="option.selected"
|
||||
class="m-0 border-[1.5px] shadow border-slate-200 dark:border-slate-600 appearance-none rounded-[4px] w-4 h-4 dark:bg-slate-800 focus:ring-1 focus:ring-slate-100 dark:focus:ring-slate-700 checked:bg-woot-600 dark:checked:bg-woot-600 after:content-[''] after:text-white checked:after:content-['✓'] after:flex after:items-center after:justify-center checked:border-t checked:border-woot-700 dark:checked:border-woot-300 checked:border-b-0 checked:border-r-0 checked:border-l-0 after:text-center after:text-xs after:font-bold after:relative after:-top-[1.5px]"
|
||||
class="m-0 border-[1.5px] shadow border-slate-200 dark:border-slate-600 appearance-none rounded-[4px] w-4 h-4 dark:bg-slate-800 focus:ring-1 focus:ring-slate-100 dark:focus:ring-slate-700 checked:bg-n-brand dark:checked:bg-n-brand after:content-[''] after:text-white checked:after:content-['✓'] after:flex after:items-center after:justify-center checked:border-t checked:border-woot-700 dark:checked:border-woot-300 checked:border-b-0 checked:border-r-0 checked:border-l-0 after:text-center after:text-xs after:font-bold after:relative after:-top-[1.5px]"
|
||||
@change="updateDisplayOption(option)"
|
||||
/>
|
||||
<label
|
||||
|
||||
@@ -9,10 +9,14 @@ import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
import PaginationButton from './PaginationButton.vue';
|
||||
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import BackButton from 'dashboard/components/widgets/BackButton.vue';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PaginationButton,
|
||||
NextButton,
|
||||
BackButton,
|
||||
CustomSnoozeModal,
|
||||
},
|
||||
props: {
|
||||
@@ -107,45 +111,39 @@ export default {
|
||||
<div
|
||||
class="flex items-center justify-between w-full gap-2 py-2 border-b ltr:pl-4 rtl:pl-2 h-14 ltr:pr-2 rtl:pr-4 rtl:border-r border-slate-50 dark:border-slate-800/50"
|
||||
>
|
||||
<woot-button
|
||||
variant="clear link"
|
||||
class="flex md:hidden !pt-1 !pb-1 rounded-md ltr:pr-1 rtl:pl-1 !no-underline"
|
||||
size="medium"
|
||||
color-scheme="primary"
|
||||
icon="chevron-left"
|
||||
@click="onClickGoToInboxList"
|
||||
>
|
||||
{{ $t('INBOX.ACTION_HEADER.BACK') }}
|
||||
</woot-button>
|
||||
<PaginationButton
|
||||
v-if="totalLength > 1"
|
||||
:total-length="totalLength"
|
||||
:current-index="currentIndex + 1"
|
||||
@next="onClickNext"
|
||||
@prev="onClickPrev"
|
||||
/>
|
||||
<div v-else />
|
||||
<div class="flex items-center gap-4">
|
||||
<BackButton
|
||||
compact
|
||||
:button-label="$t('INBOX.ACTION_HEADER.BACK')"
|
||||
class="xl:hidden flex"
|
||||
/>
|
||||
<PaginationButton
|
||||
v-if="totalLength > 1"
|
||||
:total-length="totalLength"
|
||||
:current-index="currentIndex + 1"
|
||||
@next="onClickNext"
|
||||
@prev="onClickPrev"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<woot-button
|
||||
variant="hollow"
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
icon="snooze"
|
||||
class="[&>span]:hidden md:[&>span]:inline-flex"
|
||||
<NextButton
|
||||
:label="$t('INBOX.ACTION_HEADER.SNOOZE')"
|
||||
icon="i-lucide-bell-minus"
|
||||
slate
|
||||
xs
|
||||
outline
|
||||
class="[&>.truncate]:hidden md:[&>.truncate]:block"
|
||||
@click="openSnoozeNotificationModal"
|
||||
>
|
||||
{{ $t('INBOX.ACTION_HEADER.SNOOZE') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
icon="delete"
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
variant="hollow"
|
||||
class="[&>span]:hidden md:[&>span]:inline-flex"
|
||||
/>
|
||||
<NextButton
|
||||
:label="$t('INBOX.ACTION_HEADER.DELETE')"
|
||||
icon="i-lucide-trash-2"
|
||||
slate
|
||||
xs
|
||||
outline
|
||||
class="[&>.truncate]:hidden md:[&>.truncate]:block"
|
||||
@click="deleteNotification"
|
||||
>
|
||||
{{ $t('INBOX.ACTION_HEADER.DELETE') }}
|
||||
</woot-button>
|
||||
/>
|
||||
</div>
|
||||
<woot-modal
|
||||
v-model:show="showCustomSnoozeModal"
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script>
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import InboxOptionMenu from './InboxOptionMenu.vue';
|
||||
import InboxDisplayMenu from './InboxDisplayMenu.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NextButton,
|
||||
InboxOptionMenu,
|
||||
InboxDisplayMenu,
|
||||
},
|
||||
@@ -77,56 +80,44 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-between w-full py-2 border-b ltr:pl-4 rtl:pl-2 rtl:pr-4 ltr:pr-2 h-14 border-slate-50 dark:border-slate-800/50"
|
||||
class="flex items-center justify-between w-full gap-1 pt-4 pb-2 h-14 ltr:pl-6 rtl:pl-3 rtl:pr-6 ltr:pr-3"
|
||||
>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<h1 class="text-xl font-medium text-slate-900 dark:text-slate-25">
|
||||
<div class="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<h1
|
||||
class="min-w-0 text-xl font-medium truncate text-slate-900 dark:text-slate-25"
|
||||
>
|
||||
{{ $t('INBOX.LIST.TITLE') }}
|
||||
</h1>
|
||||
<div class="relative">
|
||||
<div
|
||||
role="button"
|
||||
class="flex items-center gap-1 px-2 py-1 border rounded-md border-slate-100 dark:border-slate-700/50"
|
||||
<NextButton
|
||||
:label="$t('INBOX.LIST.DISPLAY_DROPDOWN')"
|
||||
icon="i-lucide-chevron-down"
|
||||
trailing-icon
|
||||
slate
|
||||
xs
|
||||
faded
|
||||
@click="openInboxDisplayMenu"
|
||||
>
|
||||
<span
|
||||
class="text-xs font-medium text-center text-slate-600 dark:text-slate-200"
|
||||
>
|
||||
{{ $t('INBOX.LIST.DISPLAY_DROPDOWN') }}
|
||||
</span>
|
||||
<fluent-icon
|
||||
icon="chevron-down"
|
||||
size="12"
|
||||
class="text-slate-600 dark:text-slate-200"
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
<InboxDisplayMenu
|
||||
v-if="showInboxDisplayMenu"
|
||||
v-on-clickaway="openInboxDisplayMenu"
|
||||
class="absolute top-9 ltr:left-0 rtl:right-0"
|
||||
class="absolute mt-1 top-full ltr:left-0 rtl:right-0"
|
||||
@filter="onFilterChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative flex items-center gap-1">
|
||||
<!-- <woot-button
|
||||
variant="clear"
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
icon="filter"
|
||||
@click="openInboxFilter"
|
||||
/> -->
|
||||
<woot-button
|
||||
variant="clear"
|
||||
size="small"
|
||||
color-scheme="secondary"
|
||||
icon="mail-inbox"
|
||||
<NextButton
|
||||
icon="i-lucide-sliders-vertical"
|
||||
slate
|
||||
xs
|
||||
faded
|
||||
@click="openInboxOptionsMenu"
|
||||
/>
|
||||
<InboxOptionMenu
|
||||
v-if="showInboxOptionMenu"
|
||||
v-on-clickaway="openInboxOptionsMenu"
|
||||
class="absolute top-9 ltr:right-0 ltr:md:right-[unset] rtl:left-0 rtl:md:left-[unset]"
|
||||
class="absolute top-full mt-1 ltr:right-0 ltr:lg:right-[unset] rtl:left-0 rtl:md:left-[unset]"
|
||||
@option-click="onInboxOptionMenuClick"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<script>
|
||||
import { getInboxClassByType } from 'dashboard/helper/inbox';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
inbox: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
conversationId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
inboxIcon() {
|
||||
const { phone_number: phoneNumber, channel_type: type } = this.inbox;
|
||||
const classByType = getInboxClassByType(type, phoneNumber);
|
||||
return classByType;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="inline-flex items-center rounded-[4px] border border-slate-100 dark:border-slate-700/50 divide-x divide-slate-100 dark:divide-slate-700/50 bg-none"
|
||||
>
|
||||
<div v-if="inbox" class="flex items-center gap-0.5 py-0.5 px-1.5">
|
||||
<fluent-icon
|
||||
class="text-slate-600 dark:text-slate-300"
|
||||
:icon="inboxIcon"
|
||||
size="14"
|
||||
/>
|
||||
<span class="font-medium text-slate-600 dark:text-slate-200 text-xs">
|
||||
{{ inbox.name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center py-0.5 px-1.5">
|
||||
<span class="font-medium text-slate-600 dark:text-slate-200 text-xs">
|
||||
{{ conversationId }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="z-50 flex flex-col w-40 gap-1 py-1 bg-white border divide-y shadow-md dark:bg-slate-900 border-slate-100 dark:border-slate-700/50 rounded-xl divide-slate-100 dark:divide-slate-700/50"
|
||||
class="z-50 flex flex-col w-40 gap-1 bg-n-alpha-3 backdrop-blur-[100px] divide-y py-2 px-2 outline outline-1 outline-n-container shadow-lg rounded-xl divide-n-weak dark:divide-n-strong"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<MenuItem
|
||||
|
||||
@@ -10,7 +10,7 @@ defineProps({
|
||||
<template>
|
||||
<div
|
||||
role="button"
|
||||
class="flex items-center w-full h-8 px-2 py-1 overflow-hidden text-xs font-medium rounded-md cursor-pointer text-slate-800 dark:text-slate-100 whitespace-nowrap text-ellipsis hover:text-woot-600 dark:hover:text-woot-500"
|
||||
class="flex items-center w-full h-8 px-2 py-1 overflow-hidden text-xs font-medium rounded-md cursor-pointer text-n-slate-12 whitespace-nowrap text-ellipsis hover:text-n-blue-text"
|
||||
>
|
||||
{{ label }}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<script>
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NextButton,
|
||||
},
|
||||
props: {
|
||||
totalLength: {
|
||||
type: Number,
|
||||
@@ -37,34 +42,34 @@ export default {
|
||||
<template>
|
||||
<div class="flex gap-2 items-center">
|
||||
<div class="flex gap-1 items-center">
|
||||
<woot-button
|
||||
size="tiny"
|
||||
variant="hollow"
|
||||
color-scheme="secondary"
|
||||
icon="chevron-up"
|
||||
<NextButton
|
||||
icon="i-lucide-chevron-up"
|
||||
xs
|
||||
slate
|
||||
outline
|
||||
:disabled="isUpDisabled"
|
||||
@click="handleUpClick"
|
||||
/>
|
||||
<woot-button
|
||||
size="tiny"
|
||||
variant="hollow"
|
||||
color-scheme="secondary"
|
||||
icon="chevron-down"
|
||||
<NextButton
|
||||
icon="i-lucide-chevron-down"
|
||||
xs
|
||||
slate
|
||||
outline
|
||||
:disabled="isDownDisabled"
|
||||
@click="handleDownClick"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 whitespace-nowrap">
|
||||
<span class="text-sm font-medium text-gray-600 tabular-nums">
|
||||
<span class="text-sm font-medium text-n-slate-12 tabular-nums">
|
||||
{{ totalLength <= 1 ? '1' : currentIndex }}
|
||||
</span>
|
||||
<span
|
||||
v-if="totalLength > 1"
|
||||
class="text-sm text-slate-400 relative -top-px"
|
||||
class="text-sm text-n-slate-9 relative -top-px"
|
||||
>
|
||||
/
|
||||
</span>
|
||||
<span v-if="totalLength > 1" class="text-sm text-slate-400 tabular-nums">
|
||||
<span v-if="totalLength > 1" class="text-sm text-n-slate-9 tabular-nums">
|
||||
{{ totalLength }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
<script>
|
||||
import { CONVERSATION_PRIORITY } from 'shared/constants/messages';
|
||||
export default {
|
||||
props: {
|
||||
priority: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
CONVERSATION_PRIORITY,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inline-flex items-center justify-center rounded-md">
|
||||
<!-- High Priority -->
|
||||
<svg
|
||||
v-if="priority === CONVERSATION_PRIORITY.HIGH"
|
||||
class="h-4 w-4"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="4" y="12" width="4" height="8" rx="1" fill="#FFC291" />
|
||||
<rect x="10" y="8" width="4" height="12" rx="1" fill="#FFC291" />
|
||||
<rect x="16" y="4" width="4" height="16" rx="1" fill="#FFC291" />
|
||||
</svg>
|
||||
|
||||
<!-- Low Priority -->
|
||||
<svg
|
||||
v-if="priority === CONVERSATION_PRIORITY.LOW"
|
||||
class="h-4 w-4"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="4" y="12" width="4" height="8" rx="1" fill="#FFC291" />
|
||||
<rect x="10" y="8" width="4" height="12" rx="1" fill="#DDDDDD" />
|
||||
<rect x="16" y="4" width="4" height="16" rx="1" fill="#DDDDDD" />
|
||||
</svg>
|
||||
|
||||
<!-- Medium Priority -->
|
||||
<svg
|
||||
v-if="priority === CONVERSATION_PRIORITY.MEDIUM"
|
||||
class="h-4 w-4"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="4" y="12" width="4" height="8" rx="1" fill="#FFC291" />
|
||||
<rect x="10" y="8" width="4" height="12" rx="1" fill="#FFC291" />
|
||||
<rect x="16" y="4" width="4" height="16" rx="1" fill="#DDDDDD" />
|
||||
</svg>
|
||||
|
||||
<!-- Urgent Priority -->
|
||||
<svg
|
||||
v-if="priority === CONVERSATION_PRIORITY.URGENT"
|
||||
class="h-4 w-4"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="4" y="12" width="4" height="8" rx="1" fill="#E5484D" />
|
||||
<rect x="10" y="8" width="4" height="12" rx="1" fill="#E5484D" />
|
||||
<rect x="16" y="4" width="4" height="16" rx="1" fill="#E5484D" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,86 +0,0 @@
|
||||
<script>
|
||||
import { CONVERSATION_STATUS } from 'shared/constants/messages';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
CONVERSATION_STATUS,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inline-flex items-center justify-center rounded-md">
|
||||
<!-- Pending -->
|
||||
<svg
|
||||
v-if="status === CONVERSATION_STATUS.PENDING"
|
||||
class="h-3.5 w-3.5"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.1 0.0449978V1.8558C4.5486 2.2986 1.8 5.328 1.8 9C1.8 12.9762 5.0238 16.2 9 16.2C10.6641 16.2 12.195 15.6357 13.4154 14.688L14.6961 15.9687C13.1445 17.2377 11.16 18 9 18C4.0293 18 0 13.9707 0 9C0 4.3335 3.5523 0.495898 8.1 0.0449978ZM17.955 9.9C17.775 11.7099 17.0604 13.3623 15.9687 14.6952L14.688 13.4154C15.462 12.4191 15.9804 11.2149 16.1442 9.9H17.9559H17.955ZM9.9018 0.0449978C14.1534 0.467098 17.5338 3.8484 17.9568 8.1H16.1451C15.7392 4.8438 13.158 2.2626 9.9018 1.8558V0.0440979V0.0449978Z"
|
||||
class="fill-[#B9BBC6]"
|
||||
/>
|
||||
</svg>
|
||||
<!-- Open -->
|
||||
<svg
|
||||
v-if="status === CONVERSATION_STATUS.OPEN"
|
||||
class="h-3.5 w-3.5"
|
||||
width="19"
|
||||
height="19"
|
||||
viewBox="0 0 19 19"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.375 18.875C4.19733 18.875 0 14.6776 0 9.5C0 4.32233 4.19733 0.125 9.375 0.125C14.5526 0.125 18.75 4.32233 18.75 9.5C18.75 14.6776 14.5526 18.875 9.375 18.875ZM9.375 17C13.5172 17 16.875 13.6422 16.875 9.5C16.875 5.35786 13.5172 2 9.375 2C5.23286 2 1.875 5.35786 1.875 9.5C1.875 13.6422 5.23286 17 9.375 17Z"
|
||||
class="fill-[#ED8A5C]"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- Snoozed -->
|
||||
<svg
|
||||
v-if="status === CONVERSATION_STATUS.SNOOZED"
|
||||
class="h-3.5 w-3.5"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9 18C4.0293 18 0 13.9707 0 9C0 4.0293 4.0293 0 9 0C13.9707 0 18 4.0293 18 9C18 13.9707 13.9707 18 9 18ZM2.9961 12.9825C3.58766 13.8676 4.36812 14.6105 5.28129 15.1577C6.19446 15.7049 7.21761 16.0428 8.27707 16.147C9.33652 16.2513 10.4059 16.1193 11.4082 15.7606C12.4105 15.4019 13.3208 14.8254 14.0736 14.0726C14.8263 13.3198 15.4027 12.4094 15.7613 11.4071C16.12 10.4047 16.2518 9.33532 16.1475 8.27588C16.0431 7.21644 15.7052 6.19332 15.1579 5.2802C14.6106 4.36707 13.8676 3.58668 12.9825 2.9952C13.3706 4.3796 13.383 5.84237 13.0186 7.23318C12.6542 8.62399 11.926 9.89269 10.9089 10.9089C9.89277 11.9258 8.62423 12.6539 7.23359 13.0183C5.84296 13.3828 4.38037 13.3704 2.9961 12.9825Z"
|
||||
class="fill-[#0B68CB]"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- Resolved -->
|
||||
<svg
|
||||
v-if="status === CONVERSATION_STATUS.RESOLVED"
|
||||
class="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="3 3 17.92 17.92"
|
||||
>
|
||||
<path
|
||||
d="M11.96 20.92C7.01152 20.92 3 16.9084 3 11.96C3 7.01152 7.01152 3 11.96 3C16.9084 3 20.92 7.01152 20.92 11.96C20.92 16.9084 16.9084 20.92 11.96 20.92ZM11.96 19.128C15.9188 19.128 19.128 15.9188 19.128 11.96C19.128 8.00122 15.9188 4.792 11.96 4.792C8.00122 4.792 4.792 8.00122 4.792 11.96C4.792 15.9188 8.00122 19.128 11.96 19.128Z"
|
||||
class="fill-[#5BB98C]"
|
||||
/>
|
||||
<path
|
||||
d="M11.9599 17.9333C15.2589 17.9333 17.9332 15.2589 17.9332 11.96C17.9332 8.66098 15.2589 5.98663 11.9599 5.98663C8.66092 5.98663 5.98657 8.66098 5.98657 11.96C5.98657 15.2589 8.66092 17.9333 11.9599 17.9333Z"
|
||||
class="fill-[#5BB98C]"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
export const NOTIFICATION_TYPES_MAPPING = {
|
||||
CONVERSATION_MENTION: ['i-lucide-at-sign', 'text-n-blue-text'],
|
||||
CONVERSATION_ASSIGNMENT: ['i-lucide-chevrons-right', 'text-n-blue-text'],
|
||||
CONVERSATION_CREATION: ['i-lucide-mail-plus', 'text-n-blue-text'],
|
||||
PARTICIPATING_CONVERSATION_NEW_MESSAGE: [
|
||||
'i-lucide-message-square-plus',
|
||||
'text-n-blue-text',
|
||||
],
|
||||
ASSIGNED_CONVERSATION_NEW_MESSAGE: [
|
||||
'i-lucide-message-square-plus',
|
||||
'text-n-blue-text',
|
||||
],
|
||||
SLA_MISSED_FIRST_RESPONSE: ['i-lucide-heart-crack', 'text-n-ruby-11'],
|
||||
SLA_MISSED_NEXT_RESPONSE: ['i-lucide-heart-crack', 'text-n-ruby-11'],
|
||||
SLA_MISSED_RESOLUTION: ['i-lucide-heart-crack', 'text-n-ruby-11'],
|
||||
};
|
||||
Reference in New Issue
Block a user