using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PowderCoating.Core.Interfaces; using PowderCoating.Web.Extensions; namespace PowderCoating.Web.Controllers; [Authorize] public class InAppNotificationsController : Controller { private readonly IUnitOfWork _unitOfWork; private readonly ITenantContext _tenant; public InAppNotificationsController(IUnitOfWork unitOfWork, ITenantContext tenant) { _unitOfWork = unitOfWork; _tenant = tenant; } /// /// Displays the paginated notification history page (read and unread). SuperAdmins see only platform-level notifications (CompanyId 0) via IgnoreQueryFilters; regular users rely on the global query filter for tenant isolation. /// public async Task Index(int pageNumber = 1, int pageSize = 25) { pageNumber = Math.Max(1, pageNumber); pageSize = pageSize is 10 or 25 or 50 ? pageSize : 25; var all = _tenant.IsPlatformAdmin() ? (await _unitOfWork.InAppNotifications.FindAsync( n => !n.IsDeleted && n.CompanyId == 0, ignoreQueryFilters: true)).ToList() : (await _unitOfWork.InAppNotifications.GetAllAsync()).ToList(); var totalCount = all.Count; var tz = ViewBag.CompanyTimeZone as string; var items = all .OrderByDescending(n => n.CreatedAt) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .Select(n => new { n.Id, n.Title, n.Message, n.Link, n.NotificationType, n.IsRead, n.ReadAt, CreatedAt = n.CreatedAt }) .ToList(); ViewBag.TotalCount = totalCount; ViewBag.PageNumber = pageNumber; ViewBag.PageSize = pageSize; ViewBag.TotalPages = (int)Math.Ceiling((double)totalCount / pageSize); return View(items); } /// /// AJAX endpoint that returns the 20 most recent notifications (read and unread) for the bell dropdown. /// [HttpGet] public async Task Recent() { var all = _tenant.IsPlatformAdmin() ? (await _unitOfWork.InAppNotifications.FindAsync( n => !n.IsDeleted && n.CompanyId == 0, ignoreQueryFilters: true)).ToList() : (await _unitOfWork.InAppNotifications.GetAllAsync()).ToList(); var tz = ViewBag.CompanyTimeZone as string; var items = all .OrderByDescending(n => n.CreatedAt) .Take(20) .Select(n => new { n.Id, n.Title, n.Message, n.Link, n.NotificationType, n.IsRead, n.CreatedAt }) .ToList(); var unreadCount = items.Count(n => !n.IsRead); return Json(new { count = unreadCount, items = items.Select(n => new { n.Id, n.Title, n.Message, n.Link, n.NotificationType, n.IsRead, CreatedAt = n.CreatedAt.Tz(tz).ToString("MMM d, h:mm tt") }) }); } /// /// AJAX endpoint that returns only unread notifications (up to 20) plus an unread count for the bell badge. /// [HttpGet] public async Task Unread() { var items = _tenant.IsPlatformAdmin() ? (await _unitOfWork.InAppNotifications.FindAsync( n => !n.IsDeleted && n.CompanyId == 0 && !n.IsRead, ignoreQueryFilters: true)) .OrderByDescending(n => n.CreatedAt).Take(20).ToList() : (await _unitOfWork.InAppNotifications.FindAsync(n => !n.IsRead)) .OrderByDescending(n => n.CreatedAt).Take(20).ToList(); var tz = ViewBag.CompanyTimeZone as string; return Json(new { count = items.Count, items = items.Select(n => new { n.Id, n.Title, n.Message, n.Link, n.NotificationType, CreatedAt = n.CreatedAt.Tz(tz).ToString("MMM d, h:mm tt") }) }); } /// /// Marks a single notification as read. Tenant isolation is enforced manually for SuperAdmins (CompanyId 0 filter) because IgnoreQueryFilters bypasses the global company filter. /// [HttpPost] public async Task MarkRead(int id) { var notification = _tenant.IsPlatformAdmin() ? await _unitOfWork.InAppNotifications.FirstOrDefaultAsync( n => n.Id == id && n.CompanyId == 0 && !n.IsDeleted, ignoreQueryFilters: true) : await _unitOfWork.InAppNotifications.GetByIdAsync(id); if (notification == null) return NotFound(); notification.IsRead = true; notification.ReadAt = DateTime.UtcNow; notification.UpdatedAt = DateTime.UtcNow; await _unitOfWork.CompleteAsync(); return Json(new { success = true }); } /// /// Marks every unread notification as read for the current user's scope in a single SaveChanges call. /// [HttpPost] public async Task MarkAllRead() { var now = DateTime.UtcNow; var unread = _tenant.IsPlatformAdmin() ? (await _unitOfWork.InAppNotifications.FindAsync( n => !n.IsDeleted && n.CompanyId == 0 && !n.IsRead, ignoreQueryFilters: true)).ToList() : (await _unitOfWork.InAppNotifications.FindAsync(n => !n.IsRead)).ToList(); foreach (var n in unread) { n.IsRead = true; n.ReadAt = now; n.UpdatedAt = now; } await _unitOfWork.CompleteAsync(); return Json(new { success = true, count = unread.Count }); } }