feat: Inbox list filter (#8880)

* feat: Inbox list filter

* fix: routes after delete/unread

* fix: Specs

* feat: Handle sort in frontend

* chore: Minor fixes

* chore: Minor fix

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
Sivin Varghese
2024-02-08 12:11:01 +05:30
committed by GitHub
parent c1d07a5471
commit 57dd979a14
15 changed files with 495 additions and 51 deletions

View File

@@ -306,7 +306,7 @@ export default {
});
} else if (isAInboxViewRoute(this.$route.name)) {
this.$router.push({
name: 'inbox-view',
name: 'inbox_view',
});
} else if (this.$route.name !== 'contacts_dashboard') {
this.$router.push({

View File

@@ -3,13 +3,13 @@
class="flex flex-col h-full w-full ltr:border-r border-slate-50 dark:border-slate-800/50"
:class="isOnExpandedLayout ? '' : 'min-w-[360px] max-w-[360px]'"
>
<inbox-list-header />
<inbox-list-header @filter="onFilterChange" />
<div
ref="notificationList"
class="flex flex-col w-full h-[calc(100%-56px)] overflow-x-hidden overflow-y-auto"
>
<inbox-card
v-for="notificationItem in records"
v-for="notificationItem in notifications"
:key="notificationItem.id"
:notification-item="notificationItem"
@mark-notification-as-read="markNotificationAsRead"
@@ -42,18 +42,20 @@
<script>
import { mapGetters } from 'vuex';
import wootConstants from 'dashboard/constants/globals';
import InboxCard from './components/InboxCard.vue';
import InboxListHeader from './components/InboxListHeader.vue';
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import IntersectionObserver from 'dashboard/components/IntersectionObserver.vue';
import alertMixin from 'shared/mixins/alertMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
export default {
components: {
InboxCard,
InboxListHeader,
IntersectionObserver,
},
mixins: [alertMixin],
mixins: [alertMixin, uiSettingsMixin],
props: {
conversationId: {
type: [String, Number],
@@ -71,38 +73,64 @@ export default {
rootMargin: '100px 0px 100px 0px',
},
page: 1,
status: '',
type: '',
sortOrder: wootConstants.INBOX_SORT_BY.NEWEST,
};
},
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
meta: 'notifications/getMeta',
records: 'notifications/getNotifications',
uiFlags: 'notifications/getUIFlags',
notification: 'notifications/getFilteredNotifications',
}),
inboxFilters() {
return {
page: this.page,
status: this.status,
type: this.type,
sortOrder: this.sortOrder,
};
},
notifications() {
return this.notification(this.inboxFilters);
},
showEndOfList() {
return this.uiFlags.isAllNotificationsLoaded && !this.uiFlags.isFetching;
},
showEmptyState() {
return !this.uiFlags.isFetching && !this.records.length;
return !this.uiFlags.isFetching && !this.notifications.length;
},
showEndOfListMessage() {
return this.showEndOfList && this.records.length;
return this.showEndOfList && this.notifications.length;
},
},
mounted() {
this.$store.dispatch('notifications/clear');
this.$store.dispatch('notifications/index', { page: 1 });
this.setSavedFilter();
this.fetchNotifications();
},
methods: {
fetchNotifications() {
this.page = 1;
this.$store.dispatch('notifications/clear');
const filter = this.inboxFilters;
this.$store.dispatch('notifications/index', filter);
},
redirectToInbox() {
if (!this.conversationId) return;
if (this.$route.name === 'inbox-view') return;
this.$router.push({ name: 'inbox-view' });
if (this.$route.name === 'inbox_view') return;
this.$router.push({ name: 'inbox_view' });
},
loadMoreNotifications() {
if (this.uiFlags.isAllNotificationsLoaded) return;
this.$store.dispatch('notifications/index', { page: this.page + 1 });
this.$store.dispatch('notifications/index', {
page: this.page + 1,
status: this.status,
type: this.type,
sortOrder: this.sortOrder,
});
this.page += 1;
},
markNotificationAsRead(notification) {
@@ -148,6 +176,25 @@ export default {
this.showAlert(this.$t('INBOX.ALERTS.DELETE'));
});
},
onFilterChange(option) {
if (option.type === wootConstants.INBOX_FILTER_TYPE.STATUS) {
this.status = option.selected ? option.key : '';
}
if (option.type === wootConstants.INBOX_FILTER_TYPE.TYPE) {
this.type = option.selected ? option.key : '';
}
if (option.type === wootConstants.INBOX_FILTER_TYPE.SORT_ORDER) {
this.sortOrder = option.key;
}
this.fetchNotifications();
},
setSavedFilter() {
const { inbox_filter_by: filterBy = {} } = this.uiSettings;
const { status, type, sort_by: sortBy } = filterBy;
this.status = status;
this.type = type;
this.sortOrder = sortBy || wootConstants.INBOX_SORT_BY.NEWEST;
},
},
};
</script>

View File

@@ -148,6 +148,7 @@ export default {
name: 'home',
});
}
this.$store.dispatch('agents/get');
},
methods: {
async fetchConversationById() {

View File

@@ -29,7 +29,8 @@
v-if="assigneeMeta"
:src="assigneeMeta.thumbnail"
:username="assigneeMeta.name"
size="20px"
size="16px"
class="relative bottom-0.5"
/>
<div class="flex min-w-0">
<span

View File

@@ -38,18 +38,17 @@
:key="option.key"
role="button"
class="flex rounded-[4px] h-5 w-full items-center justify-between p-0.5 gap-1"
:class="
activeSort === option.key ? 'bg-woot-50 dark:bg-woot-700/50' : ''
"
@click.stop="onSortOptionClick(option.key)"
:class="{
'bg-woot-50 dark:bg-woot-700/50': activeSort === option.key,
}"
@click.stop="onSortOptionClick(option)"
>
<span
class="text-xs font-medium hover:text-woot-600 dark:hover:text-woot-600"
:class="
activeSort === option.key
? 'text-woot-600 dark:text-woot-600'
: 'text-slate-600 dark:text-slate-300'
"
:class="{
'text-woot-600 dark:text-woot-600': activeSort === option.key,
'text-slate-600 dark:text-slate-300': activeSort !== option.key,
}"
>
{{ option.name }}
</span>
@@ -74,19 +73,19 @@
>
<div
v-for="option in displayOptions"
:key="option.id"
:key="option.key"
class="flex items-center px-3 py-2 gap-1.5 h-9"
>
<input
:id="option.value"
:id="option.key"
type="checkbox"
:name="option.value"
: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]"
@change="updateDisplayOption(option)"
/>
<label
:for="option.value"
:for="option.key"
class="text-xs font-medium text-slate-800 !ml-0 !mr-0 dark:text-slate-100"
>
{{ option.name }}
@@ -98,55 +97,101 @@
</template>
<script>
import wootConstants from 'dashboard/constants/globals';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
export default {
mixins: [uiSettingsMixin],
data() {
return {
showSortMenu: false,
displayOptions: [
{
id: 1,
name: this.$t('INBOX.DISPLAY_MENU.DISPLAY_OPTIONS.SNOOZED'),
value: 'snoozed',
key: wootConstants.INBOX_DISPLAY_BY.SNOOZED,
selected: false,
type: wootConstants.INBOX_FILTER_TYPE.STATUS,
},
{
id: 2,
name: this.$t('INBOX.DISPLAY_MENU.DISPLAY_OPTIONS.READ'),
value: 'read',
selected: true,
key: wootConstants.INBOX_DISPLAY_BY.READ,
selected: false,
type: wootConstants.INBOX_FILTER_TYPE.TYPE,
},
],
sortOptions: [
{
name: this.$t('INBOX.DISPLAY_MENU.SORT_OPTIONS.NEWEST'),
key: 'newest',
key: wootConstants.INBOX_SORT_BY.NEWEST,
type: wootConstants.INBOX_FILTER_TYPE.SORT_ORDER,
},
{
name: this.$t('INBOX.DISPLAY_MENU.SORT_OPTIONS.OLDEST'),
key: 'oldest',
key: wootConstants.INBOX_SORT_BY.OLDEST,
type: wootConstants.INBOX_FILTER_TYPE.SORT_ORDER,
},
],
activeSort: 'newest',
activeSort: wootConstants.INBOX_SORT_BY.NEWEST,
activeDisplayFilter: {
status: '',
type: '',
},
};
},
computed: {
activeSortOption() {
return this.sortOptions.find(option => option.key === this.activeSort)
.name;
return (
this.sortOptions.find(option => option.key === this.activeSort)?.name ||
''
);
},
},
mounted() {
this.setSavedFilter();
},
methods: {
updateDisplayOption(option) {
option.selected = !option.selected;
// TODO: Update the display options
this.displayOptions.forEach(displayOption => {
if (displayOption.key === option.key) {
displayOption.selected = !option.selected;
this.activeDisplayFilter[displayOption.type] = displayOption.selected
? displayOption.key
: '';
this.saveSelectedDisplayFilter();
this.$emit('filter', option);
}
});
},
openSortMenu() {
this.showSortMenu = !this.showSortMenu;
},
onSortOptionClick(key) {
this.activeSort = key;
onSortOptionClick(option) {
this.activeSort = option.key;
this.showSortMenu = false;
// TODO: Update the sort options
this.saveSelectedDisplayFilter();
this.$emit('filter', option);
},
saveSelectedDisplayFilter() {
this.updateUISettings({
inbox_filter_by: {
...this.activeDisplayFilter,
sort_by: this.activeSort || wootConstants.INBOX_SORT_BY.NEWEST,
},
});
},
setSavedFilter() {
const { inbox_filter_by: filterBy = {} } = this.uiSettings;
const { status, type, sort_by: sortBy } = filterBy;
this.activeSort = sortBy || wootConstants.INBOX_SORT_BY.NEWEST;
this.displayOptions.forEach(option => {
option.selected =
option.type === wootConstants.INBOX_FILTER_TYPE.STATUS
? option.key === status
: option.key === type;
this.activeDisplayFilter[option.type] = option.selected
? option.key
: '';
});
},
},
};

View File

@@ -132,7 +132,7 @@ export default {
.then(() => {
this.showAlert(this.$t('INBOX.ALERTS.DELETE'));
});
this.$router.push({ name: 'inbox-view' });
this.$router.push({ name: 'inbox_view' });
},
onClickNext() {
this.$emit('next');

View File

@@ -27,6 +27,7 @@
v-if="showInboxDisplayMenu"
v-on-clickaway="openInboxDisplayMenu"
class="absolute top-8"
@filter="onFilterChange"
/>
</div>
</div>
@@ -109,6 +110,12 @@ export default {
this.deleteAllRead();
}
},
onFilterChange(option) {
this.$emit('filter', option);
this.showInboxDisplayMenu = false;
if (this.$route.name === 'inbox_view') return;
this.$router.push({ name: 'inbox_view' });
},
},
};
</script>