diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index 9e1a16f67..439b35f71 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -2611,6 +2611,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'الاجتماعات', 'notifications.category.reminders': 'التذكيرات', 'notifications.category.important': 'مهم', + 'notifications.filterAll': 'الكل', + 'notifications.filterEmpty': 'لا توجد تنبيهات في هذه الفئة', 'about.update.status.checking': 'جارٍ التحقق...', 'about.update.status.available': 'v{version} متاح', 'about.update.status.availableNoVersion': 'تحديث متاح', diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index 1bee18079..57e3e2e77 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -2669,6 +2669,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'মিটিং', 'notifications.category.reminders': 'রিমাইন্ডার', 'notifications.category.important': 'গুরুত্বপূর্ণ', + 'notifications.filterAll': 'সব', + 'notifications.filterEmpty': 'এই বিভাগে কোনো সতর্কতা নেই', 'about.update.status.checking': 'পরীক্ষা হচ্ছে...', 'about.update.status.available': 'v{version} পাওয়া গেছে', 'about.update.status.availableNoVersion': 'আপডেট পাওয়া গেছে', diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index 886cae97a..7aca7ba1a 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -2730,6 +2730,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'Treffen', 'notifications.category.reminders': 'Erinnerungen', 'notifications.category.important': 'Wichtig', + 'notifications.filterAll': 'Alle', + 'notifications.filterEmpty': 'Keine Benachrichtigungen in dieser Kategorie', 'about.update.status.checking': 'Überprüfen...', 'about.update.status.available': 'v{version} verfügbar', 'about.update.status.availableNoVersion': 'Update verfügbar', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index dfc84facc..c5c589f6f 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -3164,6 +3164,8 @@ const en: TranslationMap = { 'notifications.category.meetings': 'Meetings', 'notifications.category.reminders': 'Reminders', 'notifications.category.important': 'Important', + 'notifications.filterAll': 'All', + 'notifications.filterEmpty': 'No alerts in this category', // About / Updates: status summary phrases 'about.update.status.checking': 'Checking...', diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index a5eb55d0f..828be5b38 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -2716,6 +2716,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'Reuniones', 'notifications.category.reminders': 'Recordatorios', 'notifications.category.important': 'Importante', + 'notifications.filterAll': 'Todas', + 'notifications.filterEmpty': 'No hay alertas en esta categoría', 'about.update.status.checking': 'Verificando...', 'about.update.status.available': 'v{version} disponible', 'about.update.status.availableNoVersion': 'Actualización disponible', diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index 2be47ecaa..e376ddea1 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -2728,6 +2728,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'Réunions', 'notifications.category.reminders': 'Rappels', 'notifications.category.important': 'Important', + 'notifications.filterAll': 'Tout', + 'notifications.filterEmpty': 'Aucune alerte dans cette catégorie', 'about.update.status.checking': 'Vérification…', 'about.update.status.available': 'v{version} disponible', 'about.update.status.availableNoVersion': 'Mise à jour disponible', diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index 1215abcf5..b7a95c9fa 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -2665,6 +2665,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'मीटिंग', 'notifications.category.reminders': 'रिमाइंडर', 'notifications.category.important': 'महत्वपूर्ण', + 'notifications.filterAll': 'सभी', + 'notifications.filterEmpty': 'इस श्रेणी में कोई सूचना नहीं', 'about.update.status.checking': 'चेक हो रहा है...', 'about.update.status.available': 'v{version} उपलब्ध है', 'about.update.status.availableNoVersion': 'अपडेट उपलब्ध है', diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index fc59d7924..c3d5db100 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -2669,6 +2669,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'Rapat', 'notifications.category.reminders': 'Pengingat', 'notifications.category.important': 'Penting', + 'notifications.filterAll': 'Semua', + 'notifications.filterEmpty': 'Tidak ada peringatan di kategori ini', 'about.update.status.checking': 'Memeriksa...', 'about.update.status.available': 'v{version} tersedia', 'about.update.status.availableNoVersion': 'Pembaruan tersedia', diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index a161dab49..86456732e 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -2709,6 +2709,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'Riunioni', 'notifications.category.reminders': 'Promemoria', 'notifications.category.important': 'Importanti', + 'notifications.filterAll': 'Tutte', + 'notifications.filterEmpty': 'Nessun avviso in questa categoria', 'about.update.status.checking': 'Verifica in corso...', 'about.update.status.available': 'v{version} disponibile', 'about.update.status.availableNoVersion': 'Aggiornamento disponibile', diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 83028b1ef..20770e134 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -2641,6 +2641,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': '회의', 'notifications.category.reminders': '리마인더', 'notifications.category.important': '중요', + 'notifications.filterAll': '전체', + 'notifications.filterEmpty': '이 카테고리에 알림이 없습니다', 'about.update.status.checking': '확인 중...', 'about.update.status.available': 'v{version} 사용 가능', 'about.update.status.availableNoVersion': '업데이트 사용 가능', diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index ac4ff09f5..6e5e4cffe 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -2691,6 +2691,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'Spotkania', 'notifications.category.reminders': 'Przypomnienia', 'notifications.category.important': 'Ważne', + 'notifications.filterAll': 'Wszystkie', + 'notifications.filterEmpty': 'Brak powiadomień w tej kategorii', 'about.update.status.checking': 'Sprawdzanie...', 'about.update.status.available': 'Dostępna wersja {version}', 'about.update.status.availableNoVersion': 'Dostępna aktualizacja', diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index a04a15c11..cbcbe0688 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -2714,6 +2714,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'Reuniões', 'notifications.category.reminders': 'Lembretes', 'notifications.category.important': 'Importante', + 'notifications.filterAll': 'Todas', + 'notifications.filterEmpty': 'Nenhum alerta nesta categoria', 'about.update.status.checking': 'Verificando...', 'about.update.status.available': 'v{version} disponível', 'about.update.status.availableNoVersion': 'Atualização disponível', diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index 7fb7b797a..4c0a0861c 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -2687,6 +2687,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': 'Встречи', 'notifications.category.reminders': 'Напоминания', 'notifications.category.important': 'Важное', + 'notifications.filterAll': 'Все', + 'notifications.filterEmpty': 'Нет уведомлений в этой категории', 'about.update.status.checking': 'Проверка...', 'about.update.status.available': 'доступна v{version}', 'about.update.status.availableNoVersion': 'Доступно обновление', diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index 91140ba13..dbc3e5093 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -2538,6 +2538,8 @@ const messages: TranslationMap = { 'notifications.category.meetings': '会议', 'notifications.category.reminders': '提醒', 'notifications.category.important': '重要', + 'notifications.filterAll': '全部', + 'notifications.filterEmpty': '此分类暂无通知', 'about.update.status.checking': '检查中...', 'about.update.status.available': 'v{version} 可用', 'about.update.status.availableNoVersion': '更新可用', diff --git a/app/src/pages/Notifications.tsx b/app/src/pages/Notifications.tsx index 4a579fc34..18515f7ce 100644 --- a/app/src/pages/Notifications.tsx +++ b/app/src/pages/Notifications.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import NotificationBody from '../components/notifications/NotificationBody'; @@ -15,6 +15,19 @@ import { selectUnreadCount, } from '../store/notificationSlice'; +// Canonical category order — drives the order chips appear in the filter row. +const CATEGORY_ORDER: NotificationCategory[] = [ + 'messages', + 'agents', + 'skills', + 'system', + 'meetings', + 'reminders', + 'important', +]; + +type CategoryFilter = NotificationCategory | 'all'; + function formatTime(ts: number, t: (key: string) => string): string { const delta = Date.now() - ts; const min = Math.floor(delta / 60000); @@ -32,6 +45,35 @@ const Notifications = () => { const dispatch = useAppDispatch(); const navigate = useNavigate(); const unread = useMemo(() => selectUnreadCount(items), [items]); + const [selectedCategory, setSelectedCategory] = useState('all'); + + // Only offer chips for categories that actually appear in the feed — no dead chips. + const presentCategories = useMemo( + () => CATEGORY_ORDER.filter(c => items.some(item => item.category === c)), + [items] + ); + + // If the active filter's category drains out of the feed, fall back to All. + const activeCategory: CategoryFilter = + selectedCategory !== 'all' && !presentCategories.includes(selectedCategory) + ? 'all' + : selectedCategory; + + // The derivation above keeps the current render correct, but the stored + // selection would otherwise stay stale — so if that category later reappears + // the filter would silently snap back to it. Reset the stored state to 'all' + // once a selected category leaves the feed so re-selection is always explicit. + useEffect(() => { + if (activeCategory !== selectedCategory) { + setSelectedCategory('all'); + } + }, [activeCategory, selectedCategory]); + + const filteredItems = useMemo( + () => + activeCategory === 'all' ? items : items.filter(item => item.category === activeCategory), + [items, activeCategory] + ); const categoryLabel = (category: NotificationCategory): string => { switch (category) { @@ -97,13 +139,47 @@ const Notifications = () => { - {items.length === 0 ? ( + {presentCategories.length > 0 && ( +
+ + {presentCategories.map(category => ( + + ))} +
+ )} + + {filteredItems.length === 0 ? (
- {t('alerts.empty')} + {activeCategory === 'all' ? t('alerts.empty') : t('notifications.filterEmpty')}
) : (