Files
PowderCoatingLogix/src/PowderCoating.Web/Views/PlatformNotifications/Index.cshtml
T
spouliot 03d3f57f7b Fix time entry workers, powder usage logging, inventory edit, and mojibake
- JobTimeEntry: migrate to UserId/UserDisplayName; make ShopWorkerId nullable
  (migration MigrateTimeEntriesToUserId)
- Log Time modal: populate worker dropdown from Identity users instead of
  ShopWorkers; fix ShopMobile view same issue
- Inventory Ledger: scan-based JobUsage transactions now appear in
  Powder Usage By Job tab (synthesized from InventoryTransaction)
- Inventory Ledger: add Edit button for JobUsage transactions; new
  GetUsageForEdit + EditUsageTransaction endpoints; inventory-ledger.js
- InventoryTransactionRepository: include Job.Customer for ledger queries
- InventoryAiLookupService: handle JSON-LD @graph wrapper (Columbia
  Coatings / WooCommerce+Yoast); add HTML price snippet fallback
- Fix mojibake in 9 views: â†' → →, âœ" → ✓, âš  → ⚠

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

235 lines
12 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.
@using PowderCoating.Core.Enums
@using PowderCoating.Web.Controllers
@model List<PlatformNotificationRow>
@{
ViewData["Title"] = "Platform Notifications";
int page = (int)ViewBag.Page;
int totalPages = (int)ViewBag.TotalPages;
}
@section Styles {
<style>
[data-bs-theme="dark"] a.text-dark { color: var(--bs-body-color) !important; }
[data-bs-theme="dark"] .badge.bg-light { background-color: var(--bs-secondary-bg) !important; color: var(--bs-body-color) !important; }
</style>
}
<div class="container-fluid py-3">
<div class="d-flex align-items-center gap-3 mb-3">
<h4 class="mb-0">
<i class="bi bi-bell me-2 text-primary"></i>Platform Notifications
</h4>
</div>
@* Summary *@
<div class="row g-3 mb-3">
<div class="col-6 col-md-3">
<div class="card text-center border-0 shadow-sm">
<div class="card-body py-2">
<div class="fs-4 fw-bold">@ViewBag.TotalCount.ToString("N0")</div>
<div class="small text-muted">Total Sent</div>
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="card text-center border-0 shadow-sm border-danger">
<div class="card-body py-2">
<div class="fs-4 fw-bold text-danger">@ViewBag.FailedCount.ToString("N0")</div>
<div class="small text-muted">Failed</div>
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="card text-center border-0 shadow-sm">
<div class="card-body py-2">
<div class="fs-4 fw-bold text-info">@ViewBag.Last24hCount.ToString("N0")</div>
<div class="small text-muted">Last 24 Hours</div>
</div>
</div>
</div>
<div class="col-6 col-md-3">
<div class="card text-center border-0 shadow-sm">
<div class="card-body py-2">
<div class="fs-4 fw-bold">@((int)ViewBag.FilteredCount)</div>
<div class="small text-muted">Filtered Results</div>
</div>
</div>
</div>
</div>
@* Filters *@
<div class="card shadow-sm mb-3">
<div class="card-body py-2">
<form method="get" class="row g-2 align-items-end">
<div class="col-12 col-md-2">
<label class="form-label small mb-1">Company</label>
<select name="companyId" class="form-select form-select-sm">
<option value="">All Companies</option>
@foreach (var c in (IEnumerable<dynamic>)ViewBag.Companies)
{
<option value="@c.Id" selected="@(ViewBag.CompanyIdFilter?.ToString() == c.Id.ToString())">@c.CompanyName</option>
}
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label small mb-1">Type</label>
<select name="type" class="form-select form-select-sm">
<option value="">All Types</option>
@foreach (var t in Enum.GetValues<NotificationType>())
{
<option value="@t" selected="@(ViewBag.TypeFilter == t.ToString())">@t</option>
}
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label small mb-1">Status</label>
<select name="status" class="form-select form-select-sm">
<option value="">All</option>
@foreach (var s in Enum.GetValues<NotificationStatus>())
{
<option value="@s" selected="@(ViewBag.StatusFilter == s.ToString())">@s</option>
}
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label small mb-1">Channel</label>
<select name="channel" class="form-select form-select-sm">
<option value="">All</option>
@foreach (var ch in Enum.GetValues<NotificationChannel>())
{
<option value="@ch" selected="@(ViewBag.ChannelFilter == ch.ToString())">@ch</option>
}
</select>
</div>
<div class="col-6 col-md-2">
<label class="form-label small mb-1">From</label>
<input type="date" name="from" class="form-control form-control-sm" value="@ViewBag.From" />
</div>
<div class="col-6 col-md-2">
<label class="form-label small mb-1">To</label>
<input type="date" name="to" class="form-control form-control-sm" value="@ViewBag.To" />
</div>
<div class="col-12 col-md-3">
<label class="form-label small mb-1">Search recipient / subject</label>
<input type="text" name="search" class="form-control form-control-sm" value="@ViewBag.Search" />
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary btn-sm">Filter</button>
<a href="@Url.Action("Index")" class="btn btn-outline-secondary btn-sm ms-1">Clear</a>
</div>
</form>
</div>
</div>
@* Table *@
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr>
<th>Sent</th>
<th>Company</th>
<th>Type</th>
<th>Channel</th>
<th>Recipient</th>
<th>Subject</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
@if (!Model.Any())
{
<tr><td colspan="8" class="text-center text-muted py-4">No notifications found.</td></tr>
}
@foreach (var row in Model)
{
var sc = row.Status == NotificationStatus.Sent ? "success"
: row.Status == NotificationStatus.Failed ? "danger"
: "secondary";
<tr>
<td class="small">@row.SentAt.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd/yy HH:mm")</td>
<td class="small">@(row.CompanyName ?? $"#{row.CompanyId}")</td>
<td><span class="badge bg-secondary-subtle text-body border small">@row.NotificationType</span></td>
<td class="small">@row.Channel</td>
<td class="small">@row.RecipientName<br><span class="text-muted">@row.Recipient</span></td>
<td class="small text-truncate" style="max-width:200px">@row.Subject</td>
<td><span class="badge bg-@sc">@row.Status</span></td>
<td>
<a asp-action="Details" asp-route-id="@row.Id" class="btn btn-outline-secondary btn-sm py-0">View</a>
</td>
</tr>
}
</tbody>
</table>
</div>
<!-- Mobile card view — shown on screens < 992px -->
<div class="mobile-card-view">
<div class="mobile-card-list">
@if (!Model.Any())
{
<p class="text-center text-muted py-4">No notifications found.</p>
}
@foreach (var row in Model)
{
var sc = row.Status == NotificationStatus.Sent ? "success"
: row.Status == NotificationStatus.Failed ? "danger"
: "secondary";
<a asp-action="Details" asp-route-id="@row.Id" class="mobile-data-card text-decoration-none">
<div class="mobile-card-header">
<div class="mobile-card-icon bg-primary"><i class="bi bi-bell"></i></div>
<div class="mobile-card-title">
<h6>@(row.Subject ?? row.NotificationType.ToString())</h6>
<small>@row.SentAt.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd/yy HH:mm")</small>
</div>
</div>
<div class="mobile-card-body">
<div class="mobile-card-row">
<span class="mobile-card-label">Status</span>
<span class="mobile-card-value"><span class="badge bg-@sc">@row.Status</span></span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Company</span>
<span class="mobile-card-value">@(row.CompanyName ?? $"#{row.CompanyId}")</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Recipient</span>
<span class="mobile-card-value">@(row.RecipientName ?? row.Recipient)</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Channel</span>
<span class="mobile-card-value">@row.Channel</span>
</div>
</div>
<div class="mobile-card-footer">
<span class="btn btn-sm btn-outline-secondary">View →</span>
</div>
</a>
}
</div>
</div>
@if (totalPages > 1)
{
<div class="card-footer d-flex justify-content-between align-items-center py-2">
<span class="small text-muted">Page @(page) of @(totalPages)</span>
<nav>
<ul class="pagination pagination-sm mb-0">
@if (page > 1)
{
<li class="page-item">
<a class="page-link" href="@Url.Action("Index", new { page = page - 1, pageSize = ViewBag.PageSize, companyId = ViewBag.CompanyIdFilter, type = ViewBag.TypeFilter, status = ViewBag.StatusFilter, channel = ViewBag.ChannelFilter, search = ViewBag.Search, from = ViewBag.From, to = ViewBag.To })"></a>
</li>
}
@if (page < totalPages)
{
<li class="page-item">
<a class="page-link" href="@Url.Action("Index", new { page = page + 1, pageSize = ViewBag.PageSize, companyId = ViewBag.CompanyIdFilter, type = ViewBag.TypeFilter, status = ViewBag.StatusFilter, channel = ViewBag.ChannelFilter, search = ViewBag.Search, from = ViewBag.From, to = ViewBag.To })"></a>
</li>
}
</ul>
</nav>
</div>
}
</div>
</div>