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:
@@ -6,8 +6,15 @@ class NotificationsAPI extends ApiClient {
|
|||||||
super('notifications', { accountScoped: true });
|
super('notifications', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
get(page) {
|
get({ page, status, type, sortOrder }) {
|
||||||
return axios.get(`${this.url}?page=${page}`);
|
return axios.get(this.url, {
|
||||||
|
params: {
|
||||||
|
page,
|
||||||
|
status,
|
||||||
|
type,
|
||||||
|
sort_order: sortOrder,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getNotifications(contactId) {
|
getNotifications(contactId) {
|
||||||
|
|||||||
@@ -28,10 +28,20 @@ describe('#NotificationAPI', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('#get', () => {
|
it('#get', () => {
|
||||||
notificationsAPI.get(1);
|
notificationsAPI.get({
|
||||||
expect(axiosMock.get).toHaveBeenCalledWith(
|
page: 1,
|
||||||
'/api/v1/notifications?page=1'
|
status: 'read',
|
||||||
);
|
type: 'Conversation',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
});
|
||||||
|
expect(axiosMock.get).toHaveBeenCalledWith('/api/v1/notifications', {
|
||||||
|
params: {
|
||||||
|
page: 1,
|
||||||
|
status: 'read',
|
||||||
|
type: 'Conversation',
|
||||||
|
sort_order: 'desc',
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#getNotifications', () => {
|
it('#getNotifications', () => {
|
||||||
@@ -65,5 +75,30 @@ describe('#NotificationAPI', () => {
|
|||||||
'/api/v1/notifications/read_all'
|
'/api/v1/notifications/read_all'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#snooze', () => {
|
||||||
|
notificationsAPI.snooze({ id: 1, snoozedUntil: 12332211 });
|
||||||
|
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||||
|
'/api/v1/notifications/1/snooze',
|
||||||
|
{
|
||||||
|
snoozed_until: 12332211,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#delete', () => {
|
||||||
|
notificationsAPI.delete(1);
|
||||||
|
expect(axiosMock.delete).toHaveBeenCalledWith('/api/v1/notifications/1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#deleteAll', () => {
|
||||||
|
notificationsAPI.deleteAll({ type: 'all' });
|
||||||
|
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||||
|
'/api/v1/notifications/destroy_all',
|
||||||
|
{
|
||||||
|
type: 'all',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -46,5 +46,18 @@ export default {
|
|||||||
},
|
},
|
||||||
EXAMPLE_URL: 'https://example.com',
|
EXAMPLE_URL: 'https://example.com',
|
||||||
EXAMPLE_WEBHOOK_URL: 'https://example/api/webhook',
|
EXAMPLE_WEBHOOK_URL: 'https://example/api/webhook',
|
||||||
|
INBOX_SORT_BY: {
|
||||||
|
NEWEST: 'desc',
|
||||||
|
OLDEST: 'asc',
|
||||||
|
},
|
||||||
|
INBOX_DISPLAY_BY: {
|
||||||
|
SNOOZED: 'snoozed',
|
||||||
|
READ: 'read',
|
||||||
|
},
|
||||||
|
INBOX_FILTER_TYPE: {
|
||||||
|
STATUS: 'status',
|
||||||
|
TYPE: 'type',
|
||||||
|
SORT_ORDER: 'sort_order',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
export const DEFAULT_REDIRECT_URL = '/app/';
|
export const DEFAULT_REDIRECT_URL = '/app/';
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ export default {
|
|||||||
});
|
});
|
||||||
} else if (isAInboxViewRoute(this.$route.name)) {
|
} else if (isAInboxViewRoute(this.$route.name)) {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'inbox-view',
|
name: 'inbox_view',
|
||||||
});
|
});
|
||||||
} else if (this.$route.name !== 'contacts_dashboard') {
|
} else if (this.$route.name !== 'contacts_dashboard') {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
class="flex flex-col h-full w-full ltr:border-r border-slate-50 dark:border-slate-800/50"
|
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]'"
|
:class="isOnExpandedLayout ? '' : 'min-w-[360px] max-w-[360px]'"
|
||||||
>
|
>
|
||||||
<inbox-list-header />
|
<inbox-list-header @filter="onFilterChange" />
|
||||||
<div
|
<div
|
||||||
ref="notificationList"
|
ref="notificationList"
|
||||||
class="flex flex-col w-full h-[calc(100%-56px)] overflow-x-hidden overflow-y-auto"
|
class="flex flex-col w-full h-[calc(100%-56px)] overflow-x-hidden overflow-y-auto"
|
||||||
>
|
>
|
||||||
<inbox-card
|
<inbox-card
|
||||||
v-for="notificationItem in records"
|
v-for="notificationItem in notifications"
|
||||||
:key="notificationItem.id"
|
:key="notificationItem.id"
|
||||||
:notification-item="notificationItem"
|
:notification-item="notificationItem"
|
||||||
@mark-notification-as-read="markNotificationAsRead"
|
@mark-notification-as-read="markNotificationAsRead"
|
||||||
@@ -42,18 +42,20 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import wootConstants from 'dashboard/constants/globals';
|
||||||
import InboxCard from './components/InboxCard.vue';
|
import InboxCard from './components/InboxCard.vue';
|
||||||
import InboxListHeader from './components/InboxListHeader.vue';
|
import InboxListHeader from './components/InboxListHeader.vue';
|
||||||
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||||
import IntersectionObserver from 'dashboard/components/IntersectionObserver.vue';
|
import IntersectionObserver from 'dashboard/components/IntersectionObserver.vue';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
InboxCard,
|
InboxCard,
|
||||||
InboxListHeader,
|
InboxListHeader,
|
||||||
IntersectionObserver,
|
IntersectionObserver,
|
||||||
},
|
},
|
||||||
mixins: [alertMixin],
|
mixins: [alertMixin, uiSettingsMixin],
|
||||||
props: {
|
props: {
|
||||||
conversationId: {
|
conversationId: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
@@ -71,38 +73,64 @@ export default {
|
|||||||
rootMargin: '100px 0px 100px 0px',
|
rootMargin: '100px 0px 100px 0px',
|
||||||
},
|
},
|
||||||
page: 1,
|
page: 1,
|
||||||
|
status: '',
|
||||||
|
type: '',
|
||||||
|
sortOrder: wootConstants.INBOX_SORT_BY.NEWEST,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
accountId: 'getCurrentAccountId',
|
accountId: 'getCurrentAccountId',
|
||||||
meta: 'notifications/getMeta',
|
meta: 'notifications/getMeta',
|
||||||
records: 'notifications/getNotifications',
|
|
||||||
uiFlags: 'notifications/getUIFlags',
|
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() {
|
showEndOfList() {
|
||||||
return this.uiFlags.isAllNotificationsLoaded && !this.uiFlags.isFetching;
|
return this.uiFlags.isAllNotificationsLoaded && !this.uiFlags.isFetching;
|
||||||
},
|
},
|
||||||
showEmptyState() {
|
showEmptyState() {
|
||||||
return !this.uiFlags.isFetching && !this.records.length;
|
return !this.uiFlags.isFetching && !this.notifications.length;
|
||||||
},
|
},
|
||||||
showEndOfListMessage() {
|
showEndOfListMessage() {
|
||||||
return this.showEndOfList && this.records.length;
|
return this.showEndOfList && this.notifications.length;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch('notifications/clear');
|
this.setSavedFilter();
|
||||||
this.$store.dispatch('notifications/index', { page: 1 });
|
this.fetchNotifications();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
fetchNotifications() {
|
||||||
|
this.page = 1;
|
||||||
|
this.$store.dispatch('notifications/clear');
|
||||||
|
const filter = this.inboxFilters;
|
||||||
|
|
||||||
|
this.$store.dispatch('notifications/index', filter);
|
||||||
|
},
|
||||||
redirectToInbox() {
|
redirectToInbox() {
|
||||||
if (!this.conversationId) return;
|
if (!this.conversationId) return;
|
||||||
if (this.$route.name === 'inbox-view') return;
|
if (this.$route.name === 'inbox_view') return;
|
||||||
this.$router.push({ name: 'inbox-view' });
|
this.$router.push({ name: 'inbox_view' });
|
||||||
},
|
},
|
||||||
loadMoreNotifications() {
|
loadMoreNotifications() {
|
||||||
if (this.uiFlags.isAllNotificationsLoaded) return;
|
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;
|
this.page += 1;
|
||||||
},
|
},
|
||||||
markNotificationAsRead(notification) {
|
markNotificationAsRead(notification) {
|
||||||
@@ -148,6 +176,25 @@ export default {
|
|||||||
this.showAlert(this.$t('INBOX.ALERTS.DELETE'));
|
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>
|
</script>
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ export default {
|
|||||||
name: 'home',
|
name: 'home',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.$store.dispatch('agents/get');
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchConversationById() {
|
async fetchConversationById() {
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
v-if="assigneeMeta"
|
v-if="assigneeMeta"
|
||||||
:src="assigneeMeta.thumbnail"
|
:src="assigneeMeta.thumbnail"
|
||||||
:username="assigneeMeta.name"
|
:username="assigneeMeta.name"
|
||||||
size="20px"
|
size="16px"
|
||||||
|
class="relative bottom-0.5"
|
||||||
/>
|
/>
|
||||||
<div class="flex min-w-0">
|
<div class="flex min-w-0">
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -38,18 +38,17 @@
|
|||||||
:key="option.key"
|
:key="option.key"
|
||||||
role="button"
|
role="button"
|
||||||
class="flex rounded-[4px] h-5 w-full items-center justify-between p-0.5 gap-1"
|
class="flex rounded-[4px] h-5 w-full items-center justify-between p-0.5 gap-1"
|
||||||
:class="
|
:class="{
|
||||||
activeSort === option.key ? 'bg-woot-50 dark:bg-woot-700/50' : ''
|
'bg-woot-50 dark:bg-woot-700/50': activeSort === option.key,
|
||||||
"
|
}"
|
||||||
@click.stop="onSortOptionClick(option.key)"
|
@click.stop="onSortOptionClick(option)"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="text-xs font-medium hover:text-woot-600 dark:hover:text-woot-600"
|
class="text-xs font-medium hover:text-woot-600 dark:hover:text-woot-600"
|
||||||
:class="
|
:class="{
|
||||||
activeSort === option.key
|
'text-woot-600 dark:text-woot-600': activeSort === option.key,
|
||||||
? 'text-woot-600 dark:text-woot-600'
|
'text-slate-600 dark:text-slate-300': activeSort !== option.key,
|
||||||
: 'text-slate-600 dark:text-slate-300'
|
}"
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{{ option.name }}
|
{{ option.name }}
|
||||||
</span>
|
</span>
|
||||||
@@ -74,19 +73,19 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="option in displayOptions"
|
v-for="option in displayOptions"
|
||||||
:key="option.id"
|
:key="option.key"
|
||||||
class="flex items-center px-3 py-2 gap-1.5 h-9"
|
class="flex items-center px-3 py-2 gap-1.5 h-9"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
:id="option.value"
|
:id="option.key"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:name="option.value"
|
:name="option.key"
|
||||||
:checked="option.selected"
|
: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-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)"
|
@change="updateDisplayOption(option)"
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
:for="option.value"
|
:for="option.key"
|
||||||
class="text-xs font-medium text-slate-800 !ml-0 !mr-0 dark:text-slate-100"
|
class="text-xs font-medium text-slate-800 !ml-0 !mr-0 dark:text-slate-100"
|
||||||
>
|
>
|
||||||
{{ option.name }}
|
{{ option.name }}
|
||||||
@@ -98,55 +97,101 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import wootConstants from 'dashboard/constants/globals';
|
||||||
|
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [uiSettingsMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showSortMenu: false,
|
showSortMenu: false,
|
||||||
displayOptions: [
|
displayOptions: [
|
||||||
{
|
{
|
||||||
id: 1,
|
|
||||||
name: this.$t('INBOX.DISPLAY_MENU.DISPLAY_OPTIONS.SNOOZED'),
|
name: this.$t('INBOX.DISPLAY_MENU.DISPLAY_OPTIONS.SNOOZED'),
|
||||||
value: 'snoozed',
|
key: wootConstants.INBOX_DISPLAY_BY.SNOOZED,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
type: wootConstants.INBOX_FILTER_TYPE.STATUS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
|
||||||
name: this.$t('INBOX.DISPLAY_MENU.DISPLAY_OPTIONS.READ'),
|
name: this.$t('INBOX.DISPLAY_MENU.DISPLAY_OPTIONS.READ'),
|
||||||
value: 'read',
|
key: wootConstants.INBOX_DISPLAY_BY.READ,
|
||||||
selected: true,
|
selected: false,
|
||||||
|
type: wootConstants.INBOX_FILTER_TYPE.TYPE,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
sortOptions: [
|
sortOptions: [
|
||||||
{
|
{
|
||||||
name: this.$t('INBOX.DISPLAY_MENU.SORT_OPTIONS.NEWEST'),
|
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'),
|
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: {
|
computed: {
|
||||||
activeSortOption() {
|
activeSortOption() {
|
||||||
return this.sortOptions.find(option => option.key === this.activeSort)
|
return (
|
||||||
.name;
|
this.sortOptions.find(option => option.key === this.activeSort)?.name ||
|
||||||
|
''
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setSavedFilter();
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateDisplayOption(option) {
|
updateDisplayOption(option) {
|
||||||
option.selected = !option.selected;
|
this.displayOptions.forEach(displayOption => {
|
||||||
// TODO: Update the display options
|
if (displayOption.key === option.key) {
|
||||||
|
displayOption.selected = !option.selected;
|
||||||
|
this.activeDisplayFilter[displayOption.type] = displayOption.selected
|
||||||
|
? displayOption.key
|
||||||
|
: '';
|
||||||
|
this.saveSelectedDisplayFilter();
|
||||||
|
this.$emit('filter', option);
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
openSortMenu() {
|
openSortMenu() {
|
||||||
this.showSortMenu = !this.showSortMenu;
|
this.showSortMenu = !this.showSortMenu;
|
||||||
},
|
},
|
||||||
onSortOptionClick(key) {
|
onSortOptionClick(option) {
|
||||||
this.activeSort = key;
|
this.activeSort = option.key;
|
||||||
this.showSortMenu = false;
|
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
|
||||||
|
: '';
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export default {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
this.showAlert(this.$t('INBOX.ALERTS.DELETE'));
|
this.showAlert(this.$t('INBOX.ALERTS.DELETE'));
|
||||||
});
|
});
|
||||||
this.$router.push({ name: 'inbox-view' });
|
this.$router.push({ name: 'inbox_view' });
|
||||||
},
|
},
|
||||||
onClickNext() {
|
onClickNext() {
|
||||||
this.$emit('next');
|
this.$emit('next');
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
v-if="showInboxDisplayMenu"
|
v-if="showInboxDisplayMenu"
|
||||||
v-on-clickaway="openInboxDisplayMenu"
|
v-on-clickaway="openInboxDisplayMenu"
|
||||||
class="absolute top-8"
|
class="absolute top-8"
|
||||||
|
@filter="onFilterChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -109,6 +110,12 @@ export default {
|
|||||||
this.deleteAllRead();
|
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>
|
</script>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const actions = {
|
|||||||
data: {
|
data: {
|
||||||
data: { payload, meta },
|
data: { payload, meta },
|
||||||
},
|
},
|
||||||
} = await NotificationsAPI.get(page);
|
} = await NotificationsAPI.get({ page });
|
||||||
commit(types.CLEAR_NOTIFICATIONS);
|
commit(types.CLEAR_NOTIFICATIONS);
|
||||||
commit(types.SET_NOTIFICATIONS, payload);
|
commit(types.SET_NOTIFICATIONS, payload);
|
||||||
commit(types.SET_NOTIFICATIONS_META, meta);
|
commit(types.SET_NOTIFICATIONS_META, meta);
|
||||||
@@ -18,14 +18,19 @@ export const actions = {
|
|||||||
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: false });
|
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
index: async ({ commit }, { page = 1 } = {}) => {
|
index: async ({ commit }, { page = 1, status, type, sortOrder } = {}) => {
|
||||||
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: true });
|
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: true });
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: {
|
data: {
|
||||||
data: { payload, meta },
|
data: { payload, meta },
|
||||||
},
|
},
|
||||||
} = await NotificationsAPI.get(page);
|
} = await NotificationsAPI.get({
|
||||||
|
page,
|
||||||
|
status,
|
||||||
|
type,
|
||||||
|
sortOrder,
|
||||||
|
});
|
||||||
commit(types.SET_NOTIFICATIONS, payload);
|
commit(types.SET_NOTIFICATIONS, payload);
|
||||||
commit(types.SET_NOTIFICATIONS_META, meta);
|
commit(types.SET_NOTIFICATIONS_META, meta);
|
||||||
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: false });
|
commit(types.SET_NOTIFICATIONS_UI_FLAG, { isFetching: false });
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
|
import { applyInboxPageFilters, sortComparator } from './helpers';
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
getNotifications($state) {
|
getNotifications($state) {
|
||||||
return Object.values($state.records).sort((n1, n2) => n2.id - n1.id);
|
return Object.values($state.records).sort((n1, n2) => n2.id - n1.id);
|
||||||
},
|
},
|
||||||
|
getFilteredNotifications: $state => filters => {
|
||||||
|
const sortOrder = filters.sortOrder === 'desc' ? 'newest' : 'oldest';
|
||||||
|
const filteredNotifications = Object.values($state.records).filter(
|
||||||
|
notification => applyInboxPageFilters(notification, filters)
|
||||||
|
);
|
||||||
|
const sortedNotifications = filteredNotifications.sort((a, b) =>
|
||||||
|
sortComparator(a, b, sortOrder)
|
||||||
|
);
|
||||||
|
return sortedNotifications;
|
||||||
|
},
|
||||||
getUIFlags($state) {
|
getUIFlags($state) {
|
||||||
return $state.uiFlags;
|
return $state.uiFlags;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
export const filterByStatus = (snoozedUntil, filterStatus) =>
|
||||||
|
filterStatus === 'snoozed' ? !!snoozedUntil : !snoozedUntil;
|
||||||
|
|
||||||
|
export const filterByType = (readAt, filterType) =>
|
||||||
|
filterType === 'read' ? !!readAt : !readAt;
|
||||||
|
|
||||||
|
export const filterByTypeAndStatus = (
|
||||||
|
readAt,
|
||||||
|
snoozedUntil,
|
||||||
|
filterType,
|
||||||
|
filterStatus
|
||||||
|
) => {
|
||||||
|
const shouldFilterByStatus = filterByStatus(snoozedUntil, filterStatus);
|
||||||
|
const shouldFilterByType = filterByType(readAt, filterType);
|
||||||
|
return shouldFilterByStatus && shouldFilterByType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyInboxPageFilters = (notification, filters) => {
|
||||||
|
const { status, type } = filters;
|
||||||
|
const { read_at: readAt, snoozed_until: snoozedUntil } = notification;
|
||||||
|
|
||||||
|
if (status && type)
|
||||||
|
return filterByTypeAndStatus(readAt, snoozedUntil, type, status);
|
||||||
|
if (status && !type) return filterByStatus(snoozedUntil, status);
|
||||||
|
if (!status && type) return filterByType(readAt, type);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const INBOX_SORT_OPTIONS = {
|
||||||
|
newest: 'desc',
|
||||||
|
oldest: 'asc',
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortConfig = {
|
||||||
|
newest: (a, b) => b.created_at - a.created_at,
|
||||||
|
oldest: (a, b) => a.created_at - b.created_at,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sortComparator = (a, b, sortOrder) => {
|
||||||
|
const sortDirection = INBOX_SORT_OPTIONS[sortOrder];
|
||||||
|
if (sortOrder === 'newest' || sortOrder === 'oldest') {
|
||||||
|
return sortConfig[sortOrder](a, b, sortDirection);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
@@ -16,6 +16,32 @@ describe('#getters', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('getFilteredNotifications', () => {
|
||||||
|
const state = {
|
||||||
|
records: {
|
||||||
|
1: { id: 1, read_at: '2024-02-07T11:42:39.988Z', snoozed_until: null },
|
||||||
|
2: { id: 2, read_at: null, snoozed_until: null },
|
||||||
|
3: {
|
||||||
|
id: 3,
|
||||||
|
read_at: '2024-02-07T11:42:39.988Z',
|
||||||
|
snoozed_until: '2024-02-07T11:42:39.988Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const filters = {
|
||||||
|
type: 'read',
|
||||||
|
status: 'snoozed',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
};
|
||||||
|
expect(getters.getFilteredNotifications(state)(filters)).toEqual([
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
read_at: '2024-02-07T11:42:39.988Z',
|
||||||
|
snoozed_until: '2024-02-07T11:42:39.988Z',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('getUIFlags', () => {
|
it('getUIFlags', () => {
|
||||||
const state = {
|
const state = {
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import {
|
||||||
|
filterByStatus,
|
||||||
|
filterByType,
|
||||||
|
filterByTypeAndStatus,
|
||||||
|
applyInboxPageFilters,
|
||||||
|
sortComparator,
|
||||||
|
} from '../../notifications/helpers';
|
||||||
|
|
||||||
|
const notifications = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
read_at: '2024-02-07T11:42:39.988Z',
|
||||||
|
snoozed_until: null,
|
||||||
|
created_at: 1707328400,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
read_at: null,
|
||||||
|
snoozed_until: null,
|
||||||
|
created_at: 1707233688,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
read_at: '2024-01-07T11:42:39.988Z',
|
||||||
|
snoozed_until: null,
|
||||||
|
created_at: 1707233672,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
read_at: null,
|
||||||
|
snoozed_until: '2024-02-08T03:30:00.000Z',
|
||||||
|
created_at: 1707233667,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
read_at: '2024-02-07T10:42:39.988Z',
|
||||||
|
snoozed_until: '2024-02-08T03:30:00.000Z',
|
||||||
|
created_at: 1707233662,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
read_at: null,
|
||||||
|
snoozed_until: '2024-02-08T03:30:00.000Z',
|
||||||
|
created_at: 1707233561,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('#filterByStatus', () => {
|
||||||
|
it('returns the notifications with snoozed status', () => {
|
||||||
|
const filters = { status: 'snoozed' };
|
||||||
|
notifications.forEach(notification => {
|
||||||
|
expect(
|
||||||
|
filterByStatus(notification.snoozed_until, filters.status)
|
||||||
|
).toEqual(notification.snoozed_until !== null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns true if the notification is snoozed', () => {
|
||||||
|
const filters = { status: 'snoozed' };
|
||||||
|
expect(
|
||||||
|
filterByStatus(notifications[3].snoozed_until, filters.status)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
it('returns false if the notification is not snoozed', () => {
|
||||||
|
const filters = { status: 'snoozed' };
|
||||||
|
expect(
|
||||||
|
filterByStatus(notifications[2].snoozed_until, filters.status)
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#filterByType', () => {
|
||||||
|
it('returns the notifications with read status', () => {
|
||||||
|
const filters = { type: 'read' };
|
||||||
|
notifications.forEach(notification => {
|
||||||
|
expect(filterByType(notification.read_at, filters.type)).toEqual(
|
||||||
|
notification.read_at !== null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns true if the notification is read', () => {
|
||||||
|
const filters = { type: 'read' };
|
||||||
|
expect(filterByType(notifications[0].read_at, filters.type)).toEqual(true);
|
||||||
|
});
|
||||||
|
it('returns false if the notification is not read', () => {
|
||||||
|
const filters = { type: 'read' };
|
||||||
|
expect(filterByType(notifications[1].read_at, filters.type)).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#filterByTypeAndStatus', () => {
|
||||||
|
it('returns the notifications with type and status', () => {
|
||||||
|
const filters = { type: 'read', status: 'snoozed' };
|
||||||
|
notifications.forEach(notification => {
|
||||||
|
expect(
|
||||||
|
filterByTypeAndStatus(
|
||||||
|
notification.read_at,
|
||||||
|
notification.snoozed_until,
|
||||||
|
filters.type,
|
||||||
|
filters.status
|
||||||
|
)
|
||||||
|
).toEqual(
|
||||||
|
notification.read_at !== null && notification.snoozed_until !== null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns true if the notification is read and snoozed', () => {
|
||||||
|
const filters = { type: 'read', status: 'snoozed' };
|
||||||
|
expect(
|
||||||
|
filterByTypeAndStatus(
|
||||||
|
notifications[4].read_at,
|
||||||
|
notifications[4].snoozed_until,
|
||||||
|
filters.type,
|
||||||
|
filters.status
|
||||||
|
)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
it('returns false if the notification is not read and snoozed', () => {
|
||||||
|
const filters = { type: 'read', status: 'snoozed' };
|
||||||
|
expect(
|
||||||
|
filterByTypeAndStatus(
|
||||||
|
notifications[3].read_at,
|
||||||
|
notifications[3].snoozed_until,
|
||||||
|
filters.type,
|
||||||
|
filters.status
|
||||||
|
)
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#applyInboxPageFilters', () => {
|
||||||
|
it('returns the notifications with type and status', () => {
|
||||||
|
const filters = { type: 'read', status: 'snoozed' };
|
||||||
|
notifications.forEach(notification => {
|
||||||
|
expect(applyInboxPageFilters(notification, filters)).toEqual(
|
||||||
|
filterByTypeAndStatus(
|
||||||
|
notification.read_at,
|
||||||
|
notification.snoozed_until,
|
||||||
|
filters.type,
|
||||||
|
filters.status
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns the notifications with type only', () => {
|
||||||
|
const filters = { type: 'read', status: null };
|
||||||
|
notifications.forEach(notification => {
|
||||||
|
expect(applyInboxPageFilters(notification, filters)).toEqual(
|
||||||
|
filterByType(notification.read_at, filters.type)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns the notifications with status only', () => {
|
||||||
|
const filters = { type: null, status: 'snoozed' };
|
||||||
|
notifications.forEach(notification => {
|
||||||
|
expect(applyInboxPageFilters(notification, filters)).toEqual(
|
||||||
|
filterByStatus(notification.snoozed_until, filters.status)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('returns true if there are no filters', () => {
|
||||||
|
const filters = { type: null, status: null };
|
||||||
|
notifications.forEach(notification => {
|
||||||
|
expect(applyInboxPageFilters(notification, filters)).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#sortComparator', () => {
|
||||||
|
it('returns the notifications sorted by newest', () => {
|
||||||
|
const sortOrder = 'newest';
|
||||||
|
const sortedNotifications = [...notifications].sort((a, b) =>
|
||||||
|
sortComparator(a, b, sortOrder)
|
||||||
|
);
|
||||||
|
const expectedOrder = [
|
||||||
|
notifications[0],
|
||||||
|
notifications[1],
|
||||||
|
notifications[2],
|
||||||
|
notifications[3],
|
||||||
|
notifications[4],
|
||||||
|
notifications[5],
|
||||||
|
].sort((a, b) => b.created_at - a.created_at);
|
||||||
|
expect(sortedNotifications).toEqual(expectedOrder);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the notifications sorted by oldest', () => {
|
||||||
|
const sortOrder = 'oldest';
|
||||||
|
const sortedNotifications = [...notifications].sort((a, b) =>
|
||||||
|
sortComparator(a, b, sortOrder)
|
||||||
|
);
|
||||||
|
const expectedOrder = [
|
||||||
|
notifications[0],
|
||||||
|
notifications[1],
|
||||||
|
notifications[2],
|
||||||
|
notifications[3],
|
||||||
|
notifications[4],
|
||||||
|
notifications[5],
|
||||||
|
].sort((a, b) => a.created_at - b.created_at);
|
||||||
|
expect(sortedNotifications).toEqual(expectedOrder);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user