Skip to content

Commit 682cfef

Browse files
author
CIS Guru
committed
Phase 2.6 In-App Notifications Complete.
1 parent 8afe816 commit 682cfef

9 files changed

Lines changed: 664 additions & 26 deletions

File tree

Aquiis.Professional/Application/Services/NotificationService.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,34 @@ public async Task<List<Notification>> GetNotificationHistoryAsync(int count = 10
160160
.ToListAsync();
161161
}
162162

163+
/// <summary>
164+
/// Get notification preferences for current user
165+
/// </summary>
166+
public async Task<NotificationPreferences> GetUserPreferencesAsync()
167+
{
168+
var userId = await _userContext.GetUserIdAsync();
169+
return await GetNotificationPreferencesAsync(userId);
170+
}
171+
172+
/// <summary>
173+
/// Update notification preferences for current user
174+
/// </summary>
175+
public async Task<NotificationPreferences> UpdateUserPreferencesAsync(NotificationPreferences preferences)
176+
{
177+
var userId = await _userContext.GetUserIdAsync();
178+
var organizationId = await _userContext.GetActiveOrganizationIdAsync();
179+
180+
// Ensure the preferences belong to the current user and organization
181+
if (preferences.UserId != userId || preferences.OrganizationId != organizationId)
182+
{
183+
throw new UnauthorizedAccessException("Cannot update preferences for another user");
184+
}
185+
186+
_context.NotificationPreferences.Update(preferences);
187+
await _context.SaveChangesAsync();
188+
return preferences;
189+
}
190+
163191
/// <summary>
164192
/// Get or create notification preferences for user
165193
/// </summary>

Aquiis.Professional/Core/Constants/NotificationConstants.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ public static class Types
1010

1111
public static class Categories
1212
{
13+
public const string Application = "Application";
14+
public const string Document = "Document";
15+
public const string Inspection = "Inspection";
1316
public const string Lease = "Lease";
14-
public const string Payment = "Payment";
1517
public const string Maintenance = "Maintenance";
16-
public const string Application = "Application";
18+
public const string Message = "Message";
19+
public const string Note = "Note";
20+
public const string Payment = "Payment";
1721
public const string Property = "Property";
18-
public const string Inspection = "Inspection";
19-
public const string Document = "Document";
22+
public const string Report = "Report";
23+
public const string Security = "Security";
2024
public const string System = "System";
2125
}
2226

Aquiis.Professional/Core/Entities/NotificationPreferences.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using Aquiis.Professional.Core.Entities;
44
using Aquiis.Professional.Core.Validation;
55

6+
namespace Aquiis.Professional.Core.Entities;
7+
68
public class NotificationPreferences : BaseModel
79
{
810
[RequiredGuid]

Aquiis.Professional/Features/Notifications/Pages/NotificationCenter.razor

Lines changed: 202 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
@page "/notifications"
2+
@using Aquiis.Professional.Core.Constants
23
@using Aquiis.Professional.Infrastructure.Services
34
@inject NotificationService NotificationService
45
@inject NavigationManager NavigationManager
56
@rendermode InteractiveServer
7+
@attribute [OrganizationAuthorize]
68
@namespace Aquiis.Professional.Features.Notifications.Pages
79

810
<PageTitle>Notification Center</PageTitle>
@@ -16,9 +18,97 @@
1618
Here you can manage your notifications.
1719
</p>
1820
</div>
19-
<button class="btn btn-secondary" @onclick="BackToDashboard">
20-
<i class="bi bi-arrow-left"></i> Back to Dashboard
21-
</button>
21+
<div class="d-flex gap-2">
22+
<button class="btn btn-outline-primary" @onclick="GoToPreferences">
23+
<i class="bi bi-gear-fill"></i> Preferences
24+
</button>
25+
<button class="btn btn-secondary" @onclick="BackToDashboard">
26+
<i class="bi bi-arrow-left"></i> Back to Dashboard
27+
</button>
28+
</div>
29+
</div>
30+
31+
<!-- Search and Filters -->
32+
<div class="card mb-3">
33+
<div class="card-body">
34+
<div class="row g-3">
35+
<!-- Search -->
36+
<div class="col-md-4">
37+
<div class="input-group">
38+
<span class="input-group-text">
39+
<i class="bi bi-search"></i>
40+
</span>
41+
<input type="text" class="form-control" placeholder="Search notifications..."
42+
@bind="searchText" @bind:event="oninput" @bind:after="ApplyFilters" />
43+
@if (!string.IsNullOrEmpty(searchText))
44+
{
45+
<button class="btn btn-outline-secondary" @onclick="ClearSearch">
46+
<i class="bi bi-x-lg"></i>
47+
</button>
48+
}
49+
</div>
50+
</div>
51+
52+
<!-- Category Filter -->
53+
<div class="col-md-2">
54+
<select class="form-select" @bind="filterCategory" @bind:after="ApplyFilters">
55+
<option value="">All Categories</option>
56+
<option value="@NotificationConstants.Categories.Application">Application</option>
57+
<option value="@NotificationConstants.Categories.Document">Document</option>
58+
<option value="@NotificationConstants.Categories.Inspection">Inspection</option>
59+
<option value="@NotificationConstants.Categories.Lease">Lease</option>
60+
<option value="@NotificationConstants.Categories.Maintenance">Maintenance</option>
61+
<option value="@NotificationConstants.Categories.Message">Message</option>
62+
<option value="@NotificationConstants.Categories.Note">Note</option>
63+
<option value="@NotificationConstants.Categories.Payment">Payment</option>
64+
<option value="@NotificationConstants.Categories.Property">Property</option>
65+
<option value="@NotificationConstants.Categories.Report">Report</option>
66+
<option value="@NotificationConstants.Categories.Security">Security</option>
67+
<option value="@NotificationConstants.Categories.System">System</option>
68+
</select>
69+
</div>
70+
71+
<!-- Type Filter -->
72+
<div class="col-md-2">
73+
<select class="form-select" @bind="filterType" @bind:after="ApplyFilters">
74+
<option value="">All Types</option>
75+
<option value="@NotificationConstants.Types.Error">Error</option>
76+
<option value="@NotificationConstants.Types.Info">Info</option>
77+
<option value="@NotificationConstants.Types.Success">Success</option>
78+
<option value="@NotificationConstants.Types.Warning">Warning</option>
79+
</select>
80+
</div>
81+
82+
<!-- Status Filter -->
83+
<div class="col-md-2">
84+
<select class="form-select" @bind="filterStatus" @bind:after="ApplyFilters">
85+
<option value="">All Status</option>
86+
<option value="unread">Unread</option>
87+
<option value="read">Read</option>
88+
</select>
89+
</div>
90+
91+
<!-- Mark All as Read -->
92+
<div class="col-md-2">
93+
<button class="btn btn-success w-100" @onclick="MarkAllAsRead"
94+
disabled="@(!notifications.Any(n => !n.IsRead))">
95+
<i class="bi bi-check-all"></i> Mark All Read
96+
</button>
97+
</div>
98+
</div>
99+
100+
@if (HasActiveFilters())
101+
{
102+
<div class="mt-2">
103+
<button class="btn btn-sm btn-outline-secondary" @onclick="ClearAllFilters">
104+
<i class="bi bi-x-circle"></i> Clear All Filters
105+
</button>
106+
<span class="ms-2 text-muted">
107+
Showing @filteredNotifications.Count of @notifications.Count notifications
108+
</span>
109+
</div>
110+
}
111+
</div>
22112
</div>
23113

24114
<div class="notification-list">
@@ -263,14 +353,21 @@
263353

264354
@code {
265355
private List<Notification> notifications = new List<Notification>();
356+
private List<Notification> filteredNotifications = new List<Notification>();
266357
private List<Notification> sortedNotifications = new List<Notification>();
267358
private List<Notification> pagedNotifications = new List<Notification>();
268359

269360
private Notification? selectedNotification;
270361
private bool showMessageModal = false;
271362

272363
private string sortColumn = nameof(Notification.CreatedOn);
273-
private bool sortAscending = true;
364+
private bool sortAscending = false;
365+
366+
// Filter and search properties
367+
private string searchText = "";
368+
private string filterCategory = "";
369+
private string filterType = "";
370+
private string filterStatus = "";
274371

275372
private int currentPage = 1;
276373
private int pageSize = 25;
@@ -289,14 +386,15 @@
289386
notifications = await NotificationService.GetUnreadNotificationsAsync();
290387

291388
notifications = new List<Notification>{
292-
new Notification { Id= Guid.NewGuid(), Title = "New message from John", Category = "Messages", Message = "Hey, can we meet tomorrow?", CreatedOn = DateTime.Now, IsRead = false },
293-
new Notification { Id= Guid.NewGuid(), Title = "Your report is ready", Category = "Reports", Message = "Your monthly report is now available.", CreatedOn = DateTime.Now.AddDays(-1), IsRead = false },
294-
new Notification { Id= Guid.NewGuid(), Title = "System maintenance scheduled", Category = "System", Message = "System maintenance is scheduled for tonight at 11 PM.", CreatedOn = DateTime.Now.AddDays(-5), IsRead = false },
295-
new Notification { Id= Guid.NewGuid(), Title = "New comment on your post", Category = "Comments", Message = "Alice commented on your post.", CreatedOn = DateTime.Now.AddDays(-2), IsRead = false },
296-
new Notification { Id= Guid.NewGuid(), Title = "Password will expire soon", Category = "Security", Message = "Your password will expire in 3 days.", CreatedOn = DateTime.Now.AddDays(-3), IsRead = false }
389+
new Notification { Id= Guid.NewGuid(), Title = "New message from John", Category = "Messages", Type = "Info", Message = "Hey, can we meet tomorrow?", CreatedOn = DateTime.Now, IsRead = false },
390+
new Notification { Id= Guid.NewGuid(), Title = "Your report is ready", Category = "Reports", Type = "Success", Message = "Your monthly report is now available.", CreatedOn = DateTime.Now.AddDays(-1), IsRead = false },
391+
new Notification { Id= Guid.NewGuid(), Title = "System maintenance scheduled", Category = "System", Type = "Warning", Message = "System maintenance is scheduled for tonight at 11 PM.", CreatedOn = DateTime.Now.AddDays(-5), IsRead = false },
392+
new Notification { Id= Guid.NewGuid(), Title = "New comment on your post", Category = "Comments", Type = "Info", Message = "Alice commented on your post.", CreatedOn = DateTime.Now.AddDays(-2), IsRead = false },
393+
new Notification { Id= Guid.NewGuid(), Title = "Password will expire soon", Category = "Security", Type = "Warning", Message = "Your password will expire in 3 days.", CreatedOn = DateTime.Now.AddDays(-3), IsRead = false }
297394
};
395+
396+
filteredNotifications = notifications;
298397
SortAndPaginateNotifications();
299-
300398
}
301399

302400
private void SortTable(string column)
@@ -313,24 +411,104 @@
313411
SortAndPaginateNotifications();
314412
}
315413

414+
private void ApplyFilters()
415+
{
416+
filteredNotifications = notifications.Where(n =>
417+
{
418+
// Search filter
419+
if (!string.IsNullOrEmpty(searchText))
420+
{
421+
var search = searchText.ToLower();
422+
if (!n.Title.ToLower().Contains(search) &&
423+
!n.Message.ToLower().Contains(search))
424+
{
425+
return false;
426+
}
427+
}
428+
429+
// Category filter
430+
if (!string.IsNullOrEmpty(filterCategory) && n.Category != filterCategory)
431+
{
432+
return false;
433+
}
434+
435+
// Type filter
436+
if (!string.IsNullOrEmpty(filterType) && n.Type != filterType)
437+
{
438+
return false;
439+
}
440+
441+
// Status filter
442+
if (!string.IsNullOrEmpty(filterStatus))
443+
{
444+
if (filterStatus == "read" && !n.IsRead)
445+
return false;
446+
if (filterStatus == "unread" && n.IsRead)
447+
return false;
448+
}
449+
450+
return true;
451+
}).ToList();
452+
453+
currentPage = 1;
454+
SortAndPaginateNotifications();
455+
}
456+
457+
private void ClearSearch()
458+
{
459+
searchText = "";
460+
ApplyFilters();
461+
}
462+
463+
private void ClearAllFilters()
464+
{
465+
searchText = "";
466+
filterCategory = "";
467+
filterType = "";
468+
filterStatus = "";
469+
ApplyFilters();
470+
}
471+
472+
private bool HasActiveFilters()
473+
{
474+
return !string.IsNullOrEmpty(searchText) ||
475+
!string.IsNullOrEmpty(filterCategory) ||
476+
!string.IsNullOrEmpty(filterType) ||
477+
!string.IsNullOrEmpty(filterStatus);
478+
}
479+
480+
private async Task MarkAllAsRead()
481+
{
482+
foreach (var notification in notifications.Where(n => !n.IsRead))
483+
{
484+
notification.IsRead = true;
485+
notification.ReadOn = DateTime.UtcNow;
486+
}
487+
await Task.CompletedTask;
488+
SortAndPaginateNotifications();
489+
}
490+
316491
private void SortAndPaginateNotifications()
317492
{
493+
// Use filtered notifications if filters are active
494+
var sourceList = HasActiveFilters() ? filteredNotifications : notifications;
495+
318496
// Sort
319497
sortedNotifications = sortColumn switch
320498
{
321499
nameof(Notification.Title) => sortAscending
322-
? notifications.OrderBy(n => n.Title).ToList()
323-
: notifications.OrderByDescending(n => n.Title).ToList(),
500+
? sourceList.OrderBy(n => n.Title).ToList()
501+
: sourceList.OrderByDescending(n => n.Title).ToList(),
324502
nameof(Notification.Category) => sortAscending
325-
? notifications.OrderBy(n => n.Category).ToList()
326-
: notifications.OrderByDescending(n => n.Category).ToList(),
503+
? sourceList.OrderBy(n => n.Category).ToList()
504+
: sourceList.OrderByDescending(n => n.Category).ToList(),
327505
nameof(Notification.Message) => sortAscending
328-
? notifications.OrderBy(n => n.Message).ToList()
329-
: notifications.OrderByDescending(n => n.Message).ToList(),
506+
? sourceList.OrderBy(n => n.Message).ToList()
507+
: sourceList.OrderByDescending(n => n.Message).ToList(),
330508
nameof(Notification.CreatedOn) => sortAscending
331-
? notifications.OrderBy(n => n.CreatedOn).ToList()
332-
: notifications.OrderByDescending(n => n.CreatedOn).ToList(),
333-
_ => notifications.OrderBy(n => n.CreatedOn).ToList()
509+
? sourceList.OrderBy(n => n.CreatedOn).ToList()
510+
: sourceList.OrderByDescending(n => n.CreatedOn).ToList(),
511+
_ => sourceList.OrderByDescending(n => n.CreatedOn).ToList()
334512
};
335513

336514
// Paginate
@@ -393,6 +571,11 @@
393571
NavigationManager.NavigateTo("/");
394572
}
395573

574+
private void GoToPreferences()
575+
{
576+
NavigationManager.NavigateTo("/notifications/preferences");
577+
}
578+
396579
// Modal Methods
397580
private void OpenMessageModal(Guid id)
398581
{

0 commit comments

Comments
 (0)