diff --git a/app/javascript/dashboard/components-next/message/chips/File.vue b/app/javascript/dashboard/components-next/message/chips/File.vue
index df9961c46..8638fd15d 100644
--- a/app/javascript/dashboard/components-next/message/chips/File.vue
+++ b/app/javascript/dashboard/components-next/message/chips/File.vue
@@ -16,20 +16,41 @@ const { t } = useI18n();
const fileName = computed(() => {
const url = attachment.dataUrl;
- if (url) {
- const filename = url.substring(url.lastIndexOf('/') + 1);
- return filename || t('CONVERSATION.UNKNOWN_FILE_TYPE');
+ if (!url) return t('CONVERSATION.UNKNOWN_FILE_TYPE');
+
+ try {
+ const encodedFilename = url.substring(url.lastIndexOf('/') + 1);
+ return decodeURIComponent(encodedFilename);
+ } catch {
+ return t('CONVERSATION.UNKNOWN_FILE_TYPE');
}
- return t('CONVERSATION.UNKNOWN_FILE_TYPE');
});
-const fileType = computed(() => {
- return fileName.value.split('.').pop();
-});
+const fileType = computed(() => fileName.value.split('.').pop()?.toLowerCase());
const fileNameWithoutExt = computed(() => {
const parts = fileName.value.split('.');
- return parts.slice(0, -1).join('.') || fileName.value;
+
+ // If there's no extension (no dots in filename)
+ if (parts.length === 1) {
+ return fileName.value.trim();
+ }
+
+ // Take all parts except the last one (extension)
+ const nameWithoutExt = parts.slice(0, -1).join('.');
+ return nameWithoutExt.trim();
+});
+
+const displayFileName = computed(() => {
+ const name = fileNameWithoutExt.value;
+ const truncatedName = (str, maxLength, hasExt) =>
+ str.length > maxLength
+ ? `${str.substring(0, maxLength).trimEnd()}${hasExt ? '..' : '...'}`
+ : str;
+
+ return fileType.value
+ ? `${truncatedName(name, 14, true)}.${fileType.value}`
+ : truncatedName(name, 16, false);
});
const textColorClass = computed(() => {
@@ -62,17 +83,15 @@ const textColorClass = computed(() => {
>
-
- {{ fileNameWithoutExt }}
-
- .{{ fileType }}
+ {{ displayFileName }}
{
icon: 'i-lucide-inbox',
to: accountScopedRoute('inbox_view'),
activeOn: ['inbox_view', 'inbox_view_conversation'],
+ getterKeys: {
+ badge: 'notifications/getHasUnreadNotifications',
+ },
},
{
name: 'Conversation',
diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue b/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue
index 305171d10..64345c7de 100644
--- a/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue
+++ b/app/javascript/dashboard/components-next/sidebar/SidebarGroup.vue
@@ -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"
diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarGroupHeader.vue b/app/javascript/dashboard/components-next/sidebar/SidebarGroupHeader.vue
index 160483a6a..1fd1786a0 100644
--- a/app/javascript/dashboard/components-next/sidebar/SidebarGroupHeader.vue
+++ b/app/javascript/dashboard/components-next/sidebar/SidebarGroupHeader.vue
@@ -1,7 +1,8 @@
@@ -28,7 +32,13 @@ const emit = defineEmits(['toggle']);
}"
@click.stop="emit('toggle')"
>
-
+
+
+
+
{{ label }}
diff --git a/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue b/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue
index 660147cf3..50771d538 100644
--- a/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue
+++ b/app/javascript/dashboard/routes/dashboard/inbox/InboxList.vue
@@ -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',
diff --git a/app/javascript/dashboard/store/modules/notifications/getters.js b/app/javascript/dashboard/store/modules/notifications/getters.js
index 18e78595c..4f4cafc9a 100644
--- a/app/javascript/dashboard/store/modules/notifications/getters.js
+++ b/app/javascript/dashboard/store/modules/notifications/getters.js
@@ -35,4 +35,7 @@ export const getters = {
getNotificationFilters($state) {
return $state.notificationFilters;
},
+ getHasUnreadNotifications: $state => {
+ return $state.meta.unreadCount > 0;
+ },
};
diff --git a/app/javascript/dashboard/store/modules/notifications/index.js b/app/javascript/dashboard/store/modules/notifications/index.js
index ca4f155ff..f4d45bec2 100644
--- a/app/javascript/dashboard/store/modules/notifications/index.js
+++ b/app/javascript/dashboard/store/modules/notifications/index.js
@@ -6,7 +6,7 @@ const state = {
meta: {
count: 0,
currentPage: 1,
- unReadCount: 0,
+ unreadCount: 0,
},
records: {},
uiFlags: {
diff --git a/app/javascript/dashboard/store/modules/specs/notifications/getters.spec.js b/app/javascript/dashboard/store/modules/specs/notifications/getters.spec.js
index 8721d1c70..580a3df00 100644
--- a/app/javascript/dashboard/store/modules/specs/notifications/getters.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/notifications/getters.spec.js
@@ -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);
+ });
+ });
});