Files
PowderCoatingLogix/src/PowderCoating.Web/Views/InAppNotifications/Index.cshtml
T
spouliot a0bdd2b5b4 Sweep all .cshtml files for encoding corruption; add pre-commit guard
Replace all corruption variants with HTML entities across 226 view files:
- 3-char UTF-8-as-Win1252 sequences (ae-corruption)
- Standalone smart/curly quotes that break C# Razor expressions
- Partially re-corrupted variants where the 3rd byte was normalised to ASCII

tools/Fix-Encoding.ps1: re-runnable sweep; uses [char] code points so the
script itself never contains a literal non-ASCII character; supports -DryRun

.githooks/pre-commit: blocks commits containing the ae-corruption byte
signature (xc3xa2xe2x82xac); git core.hooksPath = .githooks so the
hook is repo-committed and active for all future work on this machine.

Build clean; 225 unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 21:37:10 -04:00

216 lines
10 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@{
ViewData["Title"] = "Notification History";
ViewData["PageIcon"] = "bi-bell";
var items = Model as IEnumerable<dynamic> ?? Enumerable.Empty<dynamic>();
var pageNumber = (int)(ViewBag.PageNumber ?? 1);
var pageSize = (int)(ViewBag.PageSize ?? 25);
var totalCount = (int)(ViewBag.TotalCount ?? 0);
var totalPages = (int)(ViewBag.TotalPages ?? 1);
}
@section Styles {
<style>
tr.notif-unread { background: rgba(99, 102, 241, 0.08) !important; }
tr.notif-unread:hover { background: rgba(99, 102, 241, 0.14) !important; }
</style>
}
<div class="mb-4"></div>
@if (!items.Any())
{
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-5 text-muted">
<i class="bi bi-bell-slash fs-1 d-block mb-3 opacity-25"></i>
<p class="mb-0">No notifications yet.</p>
</div>
</div>
}
else
{
<div class="card border-0 shadow-sm">
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var n in items)
{
bool mIsRead = (bool)n.IsRead;
string mTitle = (string)n.Title;
string mMessage = (string)n.Message;
string? mLink = (string?)n.Link;
string mType = (string)n.NotificationType;
DateTime mCreatedAt = ((DateTime)n.CreatedAt).Tz(ViewBag.CompanyTimeZone as string);
<div class="mobile-data-card notif-history-row @(!mIsRead ? "notif-unread" : "")"
data-id="@n.Id"
data-title="@mTitle"
data-message="@mMessage"
data-link="@(mLink ?? "")"
data-type="@mType"
data-is-read="@(mIsRead ? "1" : "0")"
data-created-at="@mCreatedAt.ToString("MMM d, yyyy h:mm tt")">
<div class="mobile-card-header" style="@(!mIsRead ? "background:rgba(99,102,241,0.08);" : "")">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);">
<i class="bi bi-bell"></i>
</div>
<div class="mobile-card-title">
<h6 class="@(!mIsRead ? "fw-semibold" : "text-muted")">
@if (!mIsRead)
{
<span style="display:inline-block;width:8px;height:8px;background:#6366f1;border-radius:50%;margin-right:6px;"></span>
}
@mTitle
</h6>
<small>@mCreatedAt.ToString("MMM d, yyyy h:mm tt")</small>
</div>
</div>
<div class="mobile-card-body">
<div class="mobile-card-row">
<span class="mobile-card-label">Type</span>
<span class="mobile-card-value"><span class="badge bg-secondary bg-opacity-25 text-body small">@mType</span></span>
</div>
<div class="mobile-card-row" style="align-items:flex-start;">
<span class="mobile-card-label">Message</span>
<span class="mobile-card-value" style="white-space:normal;text-align:right;">@mMessage</span>
</div>
</div>
@if (!string.IsNullOrEmpty(mLink))
{
<div class="mobile-card-footer">
<a href="@mLink" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
<i class="bi bi-arrow-right me-1"></i>Open
</a>
</div>
}
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th style="width:20px;"></th>
<th>Title</th>
<th>Message</th>
<th>Type</th>
<th>Received</th>
<th>Read</th>
</tr>
</thead>
<tbody>
@foreach (var n in items)
{
bool isRead = (bool)n.IsRead;
string title = (string)n.Title;
string message = (string)n.Message;
string? link = (string?)n.Link;
string notifType = (string)n.NotificationType;
DateTime createdAt = ((DateTime)n.CreatedAt).Tz(ViewBag.CompanyTimeZone as string);
DateTime? readAt = n.ReadAt == null ? (DateTime?)null : ((DateTime)n.ReadAt).Tz(ViewBag.CompanyTimeZone as string);
<tr class="@(!isRead ? "notif-unread" : "") notif-history-row" style="cursor:pointer;"
data-id="@n.Id"
data-title="@title"
data-message="@message"
data-link="@(link ?? "")"
data-type="@notifType"
data-is-read="@(isRead ? "1" : "0")"
data-created-at="@createdAt.ToString("MMM d, yyyy h:mm tt")">
<td>
@if (!isRead)
{
<span title="Unread" style="display:inline-block;width:10px;height:10px;background:#6366f1;border-radius:50%;"></span>
}
</td>
<td class="@(!isRead ? "fw-semibold" : "text-muted")">
@if (!string.IsNullOrEmpty(link))
{
<a href="@link" class="text-decoration-none">@title</a>
}
else
{
@title
}
</td>
<td class="text-muted small" style="max-width:320px;">@message</td>
<td><span class="badge bg-secondary bg-opacity-25 text-body small">@notifType</span></td>
<td class="text-nowrap small text-muted">@createdAt.ToString("MMM d, yyyy h:mm tt")</td>
<td class="text-nowrap small text-muted">
@if (readAt.HasValue)
{
@readAt.Value.ToString("MMM d, h:mm tt")
}
else
{
<span class="badge bg-primary bg-opacity-10 text-primary">Unread</span>
}
</td>
</tr>
}
</tbody>
</table>
</div>
@if (totalPages > 1)
{
<div class="card-footer d-flex justify-content-between align-items-center">
<small class="text-muted">
Showing @((pageNumber - 1) * pageSize + 1)&ndash;@(Math.Min(pageNumber * pageSize, totalCount)) of @totalCount
</small>
<nav>
<ul class="pagination pagination-sm mb-0">
<li class="page-item @(pageNumber <= 1 ? "disabled" : "")">
<a class="page-link" href="?pageNumber=@(pageNumber - 1)&pageSize=@pageSize"></a>
</li>
@for (var p = Math.Max(1, pageNumber - 2); p <= Math.Min(totalPages, pageNumber + 2); p++)
{
<li class="page-item @(p == pageNumber ? "active" : "")">
<a class="page-link" href="?pageNumber=@p&pageSize=@pageSize">@p</a>
</li>
}
<li class="page-item @(pageNumber >= totalPages ? "disabled" : "")">
<a class="page-link" href="?pageNumber=@(pageNumber + 1)&pageSize=@pageSize"></a>
</li>
</ul>
</nav>
</div>
}
</div>
}
@section Scripts {
<script>
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.notif-history-row').forEach(row => {
row.addEventListener('click', () => {
const n = {
id: parseInt(row.dataset.id),
title: row.dataset.title,
message: row.dataset.message,
link: row.dataset.link || null,
notificationType: row.dataset.type,
isRead: row.dataset.isRead === '1',
createdAt: row.dataset.createdAt
};
// Open the shared detail modal
notifBell.openDetail(n, null);
// Update this row's visual state if it was unread
if (!n.isRead) {
row.classList.remove('notif-unread');
row.dataset.isRead = '1';
const dot = row.querySelector('[title="Unread"]');
if (dot) dot.remove();
const badge = row.querySelector('.badge.text-primary');
if (badge) badge.remove();
const titleCell = row.querySelector('td:nth-child(2)');
if (titleCell) {
titleCell.classList.remove('fw-semibold');
titleCell.classList.add('text-muted');
}
}
});
});
});
</script>
}