feat: Add unread badge to sidebar for inbox view (#10803)

# Pull Request Template

## Description

This PR adds a badge to the icon in the sidebar group header and an
unread highlight for the inbox view.

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

**Loom video**

https://www.loom.com/share/1a880aa16de249a1836b8d014325c067?sid=87cc7b64-667b-4066-8993-d33abccf7b67


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
This commit is contained in:
Sivin Varghese
2025-01-31 15:14:10 +05:30
committed by GitHub
parent b811c27ab5
commit c3aab44b5f
7 changed files with 56 additions and 9 deletions

View File

@@ -151,6 +151,9 @@ const menuItems = computed(() => {
icon: 'i-lucide-inbox',
to: accountScopedRoute('inbox_view'),
activeOn: ['inbox_view', 'inbox_view_conversation'],
getterKeys: {
badge: 'notifications/getHasUnreadNotifications',
},
},
{
name: 'Conversation',

View File

@@ -15,6 +15,7 @@ const props = defineProps({
to: { type: Object, default: null },
activeOn: { type: Array, default: () => [] },
children: { type: Array, default: undefined },
getterKeys: { type: Object, default: () => ({}) },
});
const {
@@ -143,6 +144,7 @@ onMounted(async () => {
:name
:label
:to
:getter-keys="getterKeys"
:is-active="isActive"
:has-active-child="hasActiveChild"
:expandable="hasChildren"

View File

@@ -1,7 +1,8 @@
<script setup>
import { useMapGetter } from 'dashboard/composables/store.js';
import Icon from 'next/icon/Icon.vue';
defineProps({
const props = defineProps({
to: { type: [Object, String], default: '' },
label: { type: String, default: '' },
icon: { type: [String, Object], default: '' },
@@ -9,9 +10,12 @@ defineProps({
isExpanded: { type: Boolean, default: false },
isActive: { type: Boolean, default: false },
hasActiveChild: { type: Boolean, default: false },
getterKeys: { type: Object, default: () => ({}) },
});
const emit = defineEmits(['toggle']);
const showBadge = useMapGetter(props.getterKeys.badge);
</script>
<template>
@@ -28,7 +32,13 @@ const emit = defineEmits(['toggle']);
}"
@click.stop="emit('toggle')"
>
<Icon v-if="icon" :icon="icon" class="size-4" />
<div v-if="icon" class="relative flex items-center gap-2">
<Icon v-if="icon" :icon="icon" class="size-4" />
<span
v-if="showBadge"
class="size-2 -top-px ltr:-right-px rtl:-left-px bg-n-brand absolute rounded-full border border-n-solid-2"
/>
</div>
<span class="text-sm font-medium leading-5 flex-grow">
{{ label }}
</span>

View File

@@ -121,6 +121,7 @@ export default {
})
.then(() => {
useAlert(this.$t('INBOX.ALERTS.MARK_AS_READ'));
this.$store.dispatch('notifications/unReadCount'); // to update the unread count in the store real time
});
},
markNotificationAsUnRead(notification) {
@@ -133,6 +134,7 @@ export default {
})
.then(() => {
useAlert(this.$t('INBOX.ALERTS.MARK_AS_UNREAD'));
this.$store.dispatch('notifications/unReadCount'); // to update the unread count in the store real time
});
},
deleteNotification(notification) {
@@ -186,12 +188,16 @@ export default {
notificationType,
});
this.$store.dispatch('notifications/read', {
id,
primaryActorId,
primaryActorType,
unreadCount: this.meta.unreadCount,
});
this.$store
.dispatch('notifications/read', {
id,
primaryActorId,
primaryActorType,
unreadCount: this.meta.unreadCount,
})
.then(() => {
this.$store.dispatch('notifications/unReadCount'); // to update the unread count in the store real time
});
this.$router.push({
name: 'inbox_view_conversation',

View File

@@ -35,4 +35,7 @@ export const getters = {
getNotificationFilters($state) {
return $state.notificationFilters;
},
getHasUnreadNotifications: $state => {
return $state.meta.unreadCount > 0;
},
};

View File

@@ -6,7 +6,7 @@ const state = {
meta: {
count: 0,
currentPage: 1,
unReadCount: 0,
unreadCount: 0,
},
records: {},
uiFlags: {

View File

@@ -95,4 +95,27 @@ describe('#getters', () => {
state.notificationFilters
);
});
describe('getHasUnreadNotifications', () => {
it('should return true when there are unread notifications', () => {
const state = {
meta: { unreadCount: 5 },
};
expect(getters.getHasUnreadNotifications(state)).toBe(true);
});
it('should return false when there are no unread notifications', () => {
const state = {
meta: { unreadCount: 0 },
};
expect(getters.getHasUnreadNotifications(state)).toBe(false);
});
it('should return false when meta is empty', () => {
const state = {
meta: {},
};
expect(getters.getHasUnreadNotifications(state)).toBe(false);
});
});
});