Compare commits
2 Commits
7ad7d84016
...
cf6acc125f
| Author | SHA1 | Date | |
|---|---|---|---|
| cf6acc125f | |||
| f467862877 |
@@ -109,6 +109,69 @@
|
||||
<span class="fw-semibold">Per-Company Breakdown</span>
|
||||
<span class="text-muted small">@Model.Rows.Count companies total</span>
|
||||
</div>
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var row in Model.Rows)
|
||||
{
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%);">
|
||||
<i class="bi bi-robot"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@row.CompanyName @if (!row.IsActive) { <span class="badge bg-secondary ms-1">Inactive</span> }</h6>
|
||||
<small><span class="badge bg-secondary-subtle text-secondary-emphasis border border-secondary-subtle">@row.Plan</span></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Today</span>
|
||||
<span class="mobile-card-value @(row.Today > 0 ? "fw-semibold" : "text-muted")">
|
||||
@if (row.Today > 0) { @row.Today.ToString("N0") } else { <span>—</span> }
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">30 Days</span>
|
||||
<span class="mobile-card-value @(row.Last30Days > 0 ? "fw-semibold" : "text-muted")">
|
||||
@if (row.Last30Days > 0) { @row.Last30Days.ToString("N0") } else { <span>—</span> }
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">All Time</span>
|
||||
<span class="mobile-card-value @(row.AllTime > 0 ? "" : "text-muted")">
|
||||
@if (row.AllTime > 0) { @row.AllTime.ToString("N0") } else { <span>—</span> }
|
||||
</span>
|
||||
</div>
|
||||
@if (row.TopFeature != null)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Top Feature</span>
|
||||
<span class="mobile-card-value">
|
||||
<i class="bi @FeatureIcon(row.TopFeature) me-1 text-muted"></i>@row.FeatureDisplayName(row.TopFeature)
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Tier</span>
|
||||
<span class="mobile-card-value"><span class="badge @row.TierBadgeClass">@row.UsageTier</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-controller="Companies" asp-action="Details" asp-route-id="@row.CompanyId" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-building me-1"></i>Company
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!Model.Rows.Any())
|
||||
{
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="bi bi-robot fs-1 d-block mb-2 opacity-25"></i>
|
||||
No AI usage logged yet.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle" id="aiUsageTable">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -176,6 +176,60 @@
|
||||
<div class="card-body">
|
||||
@if (Model.Items.Any())
|
||||
{
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var appointment in Model.Items)
|
||||
{
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);">
|
||||
<i class="bi bi-calendar-event"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@appointment.Title</h6>
|
||||
<small>@appointment.ScheduledStartTime.ToString("MMM dd, yyyy")<br />@(!appointment.IsAllDay ? $"{appointment.ScheduledStartTime:h:mm tt} – {appointment.ScheduledEndTime:h:mm tt}" : "All Day")</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-@appointment.StatusColorClass">@appointment.StatusDisplayName</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Type</span>
|
||||
<span class="mobile-card-value">
|
||||
<span class="badge bg-@appointment.TypeColorClass">@appointment.TypeDisplayName</span>
|
||||
</span>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(appointment.CustomerName))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Customer</span>
|
||||
<span class="mobile-card-value">@appointment.CustomerName</span>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(appointment.AssignedWorkerName))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Worker</span>
|
||||
<span class="mobile-card-value">@appointment.AssignedWorkerName</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Details" asp-route-id="@appointment.Id" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
<a asp-action="Edit" asp-route-id="@appointment.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-pencil me-1"></i>Edit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
|
||||
@@ -21,6 +21,64 @@
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var br in Model)
|
||||
{
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #14b8a6 0%, #0f766e 100%);">
|
||||
<i class="bi bi-bank"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@br.Account?.Name</h6>
|
||||
<small>Statement: @br.StatementDate.ToString("MMM d, yyyy")</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">
|
||||
@if (br.Status == BankReconciliationStatus.Completed)
|
||||
{
|
||||
<span class="badge bg-success">Completed</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-warning text-dark">In Progress</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Ending Balance</span>
|
||||
<span class="mobile-card-value fw-semibold">@br.EndingBalance.ToString("C")</span>
|
||||
</div>
|
||||
@if (br.CompletedAt.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Completed By</span>
|
||||
<span class="mobile-card-value">@br.CompletedBy</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
@if (br.Status == BankReconciliationStatus.Completed)
|
||||
{
|
||||
<a asp-action="Report" asp-route-id="@br.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-file-earmark-text me-1"></i>Report
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-action="Reconcile" asp-route-id="@br.Id" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-check2-square me-1"></i>Continue
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -60,6 +60,59 @@
|
||||
<div class="card-body p-0">
|
||||
@if (active.Any())
|
||||
{
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var ban in active)
|
||||
{
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);">
|
||||
<i class="bi bi-slash-circle"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6 class="font-monospace">@ban.IpAddress</h6>
|
||||
<small class="text-muted">@(ban.Reason ?? "No reason given")</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Banned</span>
|
||||
<span class="mobile-card-value">@ban.BannedAt.ToString("MMM d, yyyy HH:mm")</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Expires</span>
|
||||
<span class="mobile-card-value">
|
||||
@if (ban.ExpiresAt.HasValue)
|
||||
{
|
||||
<span class="badge bg-warning text-dark">@ban.ExpiresAt.Value.ToString("MMM d, yyyy")</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Permanent</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<form asp-action="Lift" asp-route-id="@ban.Id" method="post" class="d-inline"
|
||||
onsubmit="return confirm('Lift the ban on @ban.IpAddress?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-sm btn-outline-success">
|
||||
<i class="bi bi-check-circle me-1"></i>Lift
|
||||
</button>
|
||||
</form>
|
||||
<form asp-action="Delete" asp-route-id="@ban.Id" method="post" class="d-inline"
|
||||
onsubmit="return confirm('Delete ban record for @ban.IpAddress?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
@@ -130,6 +183,55 @@
|
||||
<h6 class="mb-0 text-muted"><i class="bi bi-clock-history"></i> Lifted / Expired Bans</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var ban in inactive)
|
||||
{
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);">
|
||||
<i class="bi bi-clock-history"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6 class="font-monospace">@ban.IpAddress</h6>
|
||||
<small>
|
||||
@if (!ban.IsActive)
|
||||
{
|
||||
<span class="badge bg-success">Lifted</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Expired</span>
|
||||
}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
@if (!string.IsNullOrEmpty(ban.Reason))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Reason</span>
|
||||
<span class="mobile-card-value text-muted">@ban.Reason</span>
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Banned</span>
|
||||
<span class="mobile-card-value text-muted">@ban.BannedAt.ToString("MMM d, yyyy")</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<form asp-action="Delete" asp-route-id="@ban.Id" method="post" class="d-inline"
|
||||
onsubmit="return confirm('Delete ban record for @ban.IpAddress?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash me-1"></i>Delete
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -101,6 +101,73 @@
|
||||
else
|
||||
{
|
||||
<div class="card">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var m in Model)
|
||||
{
|
||||
var expired2 = m.ExpiryDate.HasValue && m.ExpiryDate.Value < DateTime.UtcNow
|
||||
&& m.Status != CreditMemoStatus.FullyApplied
|
||||
&& m.Status != CreditMemoStatus.Voided;
|
||||
var (cmBadge, cmLabel) = m.Status switch
|
||||
{
|
||||
CreditMemoStatus.Active => ("bg-success-subtle text-success", "Active"),
|
||||
CreditMemoStatus.PartiallyApplied => ("bg-warning-subtle text-warning", "Partial"),
|
||||
CreditMemoStatus.FullyApplied => ("bg-secondary-subtle text-secondary", "Applied"),
|
||||
CreditMemoStatus.Voided => ("bg-danger-subtle text-danger", "Voided"),
|
||||
_ => ("bg-secondary-subtle text-secondary", m.Status.ToString())
|
||||
};
|
||||
var cmCustomer = string.IsNullOrWhiteSpace(m.Customer?.CompanyName)
|
||||
? $"{m.Customer?.ContactFirstName} {m.Customer?.ContactLastName}".Trim()
|
||||
: m.Customer!.CompanyName;
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = m.Id })'">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);">
|
||||
<i class="bi bi-journal-minus"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@m.MemoNumber</h6>
|
||||
<small>@cmCustomer</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 @cmBadge">@cmLabel</span></span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Amount</span>
|
||||
<span class="mobile-card-value">@m.Amount.ToString("C")</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Remaining</span>
|
||||
<span class="mobile-card-value @(m.RemainingBalance > 0 && m.Status != CreditMemoStatus.Voided ? "text-success fw-semibold" : "text-muted")">
|
||||
@m.RemainingBalance.ToString("C")
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Issued</span>
|
||||
<span class="mobile-card-value">@m.IssueDate.ToLocalTime().ToString("MM/dd/yy")</span>
|
||||
</div>
|
||||
@if (m.ExpiryDate.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Expires</span>
|
||||
<span class="mobile-card-value @(expired2 ? "text-danger fw-semibold" : "")">
|
||||
@m.ExpiryDate.Value.ToLocalTime().ToString("MM/dd/yy")
|
||||
@if (expired2) { <small>(Expired)</small> }
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Details" asp-route-id="@m.Id" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
|
||||
Details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -118,6 +118,63 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var a in Model)
|
||||
{
|
||||
var fd = a.AccumulatedDepreciation >= (a.PurchaseCost - a.SalvageValue);
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = a.Id })'">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);">
|
||||
<i class="bi bi-building-gear"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@a.Name</h6>
|
||||
<small>Purchased @a.PurchaseDate.ToLocalTime().ToString("MM/dd/yyyy")</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">
|
||||
@if (a.IsDisposed)
|
||||
{
|
||||
<span class="badge bg-secondary">Disposed</span>
|
||||
}
|
||||
else if (fd)
|
||||
{
|
||||
<span class="badge bg-light text-dark border">Fully Depreciated</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-success">Active</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Cost</span>
|
||||
<span class="mobile-card-value">@a.PurchaseCost.ToString("C")</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Book Value</span>
|
||||
<span class="mobile-card-value @(a.BookValue <= 0 ? "text-muted" : "text-success fw-semibold")">
|
||||
@a.BookValue.ToString("C")
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Monthly Depr.</span>
|
||||
<span class="mobile-card-value">@a.MonthlyDepreciation.ToString("C")</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Details" asp-route-id="@a.Id" class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation()">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -26,6 +26,48 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var cert in Model)
|
||||
{
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">
|
||||
<i class="bi bi-gift"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6 class="font-monospace">@cert.CertificateCode</h6>
|
||||
<small>@cert.OriginalAmount.ToString("C")</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Issued</span>
|
||||
<span class="mobile-card-value">@cert.IssueDate.ToLocalTime().ToString("MMM d, yyyy")</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Expiry</span>
|
||||
<span class="mobile-card-value">
|
||||
@if (cert.ExpiryDate.HasValue) { @cert.ExpiryDate.Value.ToLocalTime().ToString("MMM d, yyyy") } else { <span class="text-muted">—</span> }
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Status</span>
|
||||
<span class="mobile-card-value"><span class="badge bg-success">Active</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Details" asp-route-id="@cert.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
<a asp-action="DownloadPdf" asp-route-id="@cert.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-file-pdf me-1"></i>PDF
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -57,6 +57,73 @@ else
|
||||
{
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var cert in Model)
|
||||
{
|
||||
var (gcBadge, gcLabel) = cert.Status switch
|
||||
{
|
||||
GiftCertificateStatus.Active => ("bg-success", "Active"),
|
||||
GiftCertificateStatus.PartiallyRedeemed => ("bg-info text-dark", "Partial"),
|
||||
GiftCertificateStatus.FullyRedeemed => ("bg-secondary", "Used"),
|
||||
GiftCertificateStatus.Expired => ("bg-warning text-dark", "Expired"),
|
||||
GiftCertificateStatus.Voided => ("bg-danger", "Voided"),
|
||||
_ => ("bg-secondary", cert.Status.ToString())
|
||||
};
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = cert.Id })'">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #a855f7 0%, #7c3aed 100%);">
|
||||
<i class="bi bi-gift"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6 class="font-monospace">@cert.CertificateCode</h6>
|
||||
<small>@(cert.RecipientName ?? cert.RecipientEmail ?? "No recipient")</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 @gcBadge">@gcLabel</span></span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Face Value</span>
|
||||
<span class="mobile-card-value">@cert.OriginalAmount.ToString("C")</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Remaining</span>
|
||||
<span class="mobile-card-value @(cert.RemainingBalance > 0 ? "text-success fw-semibold" : "text-muted")">
|
||||
@cert.RemainingBalance.ToString("C")
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Issued</span>
|
||||
<span class="mobile-card-value">@cert.IssueDate.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd/yy")</span>
|
||||
</div>
|
||||
@if (cert.ExpiryDate.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Expires</span>
|
||||
<span class="mobile-card-value @(cert.ExpiryDate.Value < DateTime.Now ? "text-danger" : "")">
|
||||
@cert.ExpiryDate.Value.ToString("MM/dd/yy")
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Details" asp-route-id="@cert.Id" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
@if (cert.BatchId.HasValue)
|
||||
{
|
||||
<a asp-action="BulkResult" asp-route-batchId="@cert.BatchId" class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation()">
|
||||
<i class="bi bi-collection me-1"></i>Batch
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -29,6 +29,61 @@
|
||||
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">
|
||||
|
||||
@@ -126,6 +126,77 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@{ lastMfr = null; }
|
||||
@foreach (var item in needOrder)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(selectedMfr) && item.Manufacturer != lastMfr)
|
||||
{
|
||||
lastMfr = item.Manufacturer;
|
||||
<div class="text-uppercase fw-semibold text-muted small px-2 py-1 border-bottom mt-2">
|
||||
@(string.IsNullOrWhiteSpace(item.Manufacturer) ? "No Manufacturer" : item.Manufacturer)
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
@if (!string.IsNullOrWhiteSpace(item.ColorCode))
|
||||
{
|
||||
<div class="mobile-card-icon" style="background: @(item.ColorCode.StartsWith("#") ? item.ColorCode : "#" + item.ColorCode); border: 1px solid var(--bs-border-color);"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #64748b 0%, #475569 100%);">
|
||||
<i class="bi bi-palette"></i>
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-card-title">
|
||||
<h6>@(item.ColorName ?? item.Name)</h6>
|
||||
<small>@(item.Manufacturer ?? "No Manufacturer")</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
@if (!string.IsNullOrWhiteSpace(item.ManufacturerPartNumber))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Part #</span>
|
||||
<span class="mobile-card-value text-muted">@item.ManufacturerPartNumber</span>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(item.Finish))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Finish</span>
|
||||
<span class="mobile-card-value">@item.Finish</span>
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">In Stock</span>
|
||||
<span class="mobile-card-value">
|
||||
@if (item.QuantityOnHand > 0)
|
||||
{
|
||||
<span class="badge bg-success bg-opacity-10 text-success">@item.QuantityOnHand.ToString("N2") @item.UnitOfMeasure</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">None</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<button class="btn btn-sm btn-outline-success btn-toggle-panel"
|
||||
data-item-id="@item.Id" data-has-panel="true">
|
||||
<i class="bi bi-check-lg me-1"></i>Got It
|
||||
</button>
|
||||
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0" id="needTable">
|
||||
<thead class="table-group-divider">
|
||||
@@ -220,6 +291,68 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@{ lastMfr = null; }
|
||||
@foreach (var item in onHand)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(selectedMfr) && item.Manufacturer != lastMfr)
|
||||
{
|
||||
lastMfr = item.Manufacturer;
|
||||
<div class="text-uppercase fw-semibold text-muted small px-2 py-1 border-bottom mt-2">
|
||||
@(string.IsNullOrWhiteSpace(item.Manufacturer) ? "No Manufacturer" : item.Manufacturer)
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
@if (!string.IsNullOrWhiteSpace(item.ColorCode))
|
||||
{
|
||||
<div class="mobile-card-icon" style="background: @(item.ColorCode.StartsWith("#") ? item.ColorCode : "#" + item.ColorCode); border: 1px solid var(--bs-border-color);"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #059669 0%, #047857 100%);">
|
||||
<i class="bi bi-palette"></i>
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-card-title">
|
||||
<h6>@(item.ColorName ?? item.Name)</h6>
|
||||
<small>@(item.Manufacturer ?? "No Manufacturer")</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
@if (!string.IsNullOrWhiteSpace(item.ManufacturerPartNumber))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Part #</span>
|
||||
<span class="mobile-card-value text-muted">@item.ManufacturerPartNumber</span>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(item.Finish))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Finish</span>
|
||||
<span class="mobile-card-value">@item.Finish</span>
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Status</span>
|
||||
<span class="mobile-card-value"><span class="badge bg-success"><i class="bi bi-check-circle me-1"></i>On Wall</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<button class="btn btn-sm btn-outline-danger btn-toggle-panel"
|
||||
data-item-id="@item.Id" data-has-panel="false">
|
||||
<i class="bi bi-x-lg me-1"></i>Remove
|
||||
</button>
|
||||
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-group-divider">
|
||||
|
||||
@@ -71,6 +71,59 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var job in overdueJobs)
|
||||
{
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", "Jobs", new { id = job.JobId })'">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@job.JobNumber</h6>
|
||||
<small>@job.CustomerName</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-@job.StatusColorClass">@job.StatusDisplayName</span></span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Priority</span>
|
||||
<span class="mobile-card-value"><span class="badge bg-@job.PriorityColorClass">@job.PriorityDisplayName</span></span>
|
||||
</div>
|
||||
@if (job.ScheduledDate.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Scheduled</span>
|
||||
<span class="mobile-card-value text-danger fw-bold">@job.ScheduledDate.Value.ToString("MMM d, yyyy")</span>
|
||||
</div>
|
||||
}
|
||||
@if (job.DueDate.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Due</span>
|
||||
<span class="mobile-card-value text-danger">@job.DueDate.Value.ToString("MMM d, yyyy")</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-controller="Jobs" asp-action="Details" asp-route-id="@job.JobId" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation(); openPriorityModal(@job.JobId, @job.JobPriorityId, '@job.JobNumber')">
|
||||
<i class="bi bi-flag"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation(); openWorkerModal(@job.JobId, '@(job.AssignedUserId ?? "")', '@job.JobNumber')">
|
||||
<i class="bi bi-person"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
@@ -191,6 +244,74 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var job in Model)
|
||||
{
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", "Jobs", new { id = job.JobId })'">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);">
|
||||
<i class="bi bi-kanban"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@job.JobNumber</h6>
|
||||
<small>@job.CustomerName</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-@job.StatusColorClass" id="status-badge-@job.JobId">@job.StatusDisplayName</span></span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Priority</span>
|
||||
<span class="mobile-card-value"><span class="badge bg-@job.PriorityColorClass priority-badge-@job.JobId">@job.PriorityDisplayName</span></span>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(job.AssignedWorkerName))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Worker</span>
|
||||
<span class="mobile-card-value"><span class="badge bg-info"><i class="bi bi-person me-1"></i>@job.AssignedWorkerName</span></span>
|
||||
</div>
|
||||
}
|
||||
@if (job.ScheduledDate.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Scheduled</span>
|
||||
<span class="mobile-card-value">@job.ScheduledDate.Value.ToString("MMM d, yyyy")</span>
|
||||
</div>
|
||||
}
|
||||
@if (job.DueDate.HasValue)
|
||||
{
|
||||
var mJobOverdue = job.DueDate.Value.Date < DateTime.Today;
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Due</span>
|
||||
<span class="mobile-card-value @(mJobOverdue ? "text-danger fw-bold" : "")">@job.DueDate.Value.ToString("MMM d, yyyy")</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-controller="Jobs" asp-action="Details" asp-route-id="@job.JobId" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation(); openPriorityModal(@job.JobId, @job.JobPriorityId, '@job.JobNumber')" title="Change Priority">
|
||||
<i class="bi bi-flag"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation(); openWorkerModal(@job.JobId, '@(job.AssignedUserId ?? "")', '@job.JobNumber')" title="Assign Worker">
|
||||
<i class="bi bi-person"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="bi bi-calendar-check fs-1 d-block mb-2 opacity-25"></i>
|
||||
No jobs scheduled for @scheduledDate.ToString("MMMM dd, yyyy").
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0" id="jobsTable">
|
||||
<thead>
|
||||
@@ -352,6 +473,65 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var item in maintenanceItems)
|
||||
{
|
||||
var mPriorityBg = item.Priority switch
|
||||
{
|
||||
MaintenancePriority.Critical => "danger",
|
||||
MaintenancePriority.High => "warning",
|
||||
MaintenancePriority.Normal => "info",
|
||||
_ => "secondary"
|
||||
};
|
||||
var mStatusBgM = item.Status == MaintenanceStatus.InProgress ? "success" : "primary";
|
||||
var mStatusLbl = item.Status == MaintenanceStatus.InProgress ? "In Progress" : "Scheduled";
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", "Maintenance", new { id = item.Id })'">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #f59e0b 0%, #b45309 100%);">
|
||||
<i class="bi bi-tools"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@(item.Equipment?.EquipmentName ?? "Maintenance")</h6>
|
||||
<small>@item.MaintenanceType</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Priority</span>
|
||||
<span class="mobile-card-value"><span class="badge bg-@mPriorityBg">@item.Priority</span></span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Status</span>
|
||||
<span class="mobile-card-value"><span class="badge bg-@mStatusBgM">@mStatusLbl</span></span>
|
||||
</div>
|
||||
@if (item.AssignedUser != null)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Worker</span>
|
||||
<span class="mobile-card-value"><span class="badge bg-info text-dark"><i class="bi bi-person me-1"></i>@item.AssignedUser.FullName</span></span>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(item.Description))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Desc.</span>
|
||||
<span class="mobile-card-value text-muted">@item.Description</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-controller="Maintenance" asp-action="Details" asp-route-id="@item.Id" class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation()">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation(); openMaintenanceWorkerModal(@item.Id, '@(item.AssignedUserId ?? "")', '@(item.Equipment?.EquipmentName ?? "Maintenance")')" title="Assign Worker">
|
||||
<i class="bi bi-person"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead>
|
||||
|
||||
@@ -47,6 +47,68 @@
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var je in Model)
|
||||
{
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = je.Id })'">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);">
|
||||
<i class="bi bi-journal-text"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>
|
||||
@je.EntryNumber
|
||||
@if (je.IsReversal)
|
||||
{
|
||||
<span class="badge bg-secondary ms-1">REV</span>
|
||||
}
|
||||
</h6>
|
||||
<small>@je.EntryDate.ToString("MMM d, yyyy")</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">
|
||||
@if (je.Status == JournalEntryStatus.Draft)
|
||||
{
|
||||
<span class="badge bg-warning text-dark">Draft</span>
|
||||
}
|
||||
else if (je.Status == JournalEntryStatus.Posted)
|
||||
{
|
||||
<span class="badge bg-success">Posted</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">Reversed</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(je.Description))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Description</span>
|
||||
<span class="mobile-card-value" style="white-space:normal;text-align:right;">@je.Description</span>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(je.Reference))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Reference</span>
|
||||
<span class="mobile-card-value">@je.Reference</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Details" asp-route-id="@je.Id" class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation()">
|
||||
View
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -134,6 +134,67 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: @(item.Channel == PowderCoating.Core.Enums.NotificationChannel.Email ? "linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)" : "linear-gradient(135deg, #06b6d4 0%, #0e7490 100%)");">
|
||||
<i class="bi @(item.Channel == PowderCoating.Core.Enums.NotificationChannel.Email ? "bi-envelope" : "bi-phone")"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@item.RecipientName</h6>
|
||||
<small>@item.Recipient</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">@item.NotificationTypeDisplay</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Sent</span>
|
||||
<span class="mobile-card-value">@item.SentAt.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd HH:mm")</span>
|
||||
</div>
|
||||
@if (item.JobId.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Job</span>
|
||||
<span class="mobile-card-value">@item.JobNumber</span>
|
||||
</div>
|
||||
}
|
||||
else if (item.QuoteId.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Quote</span>
|
||||
<span class="mobile-card-value">@item.QuoteNumber</span>
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Status</span>
|
||||
<span class="mobile-card-value">
|
||||
@{
|
||||
var (mStatusBadge, mStatusIcon) = item.Status switch
|
||||
{
|
||||
PowderCoating.Core.Enums.NotificationStatus.Sent => ("bg-success", "bi-check-circle"),
|
||||
PowderCoating.Core.Enums.NotificationStatus.Failed => ("bg-danger", "bi-x-circle"),
|
||||
_ => ("bg-secondary", "bi-dash-circle")
|
||||
};
|
||||
}
|
||||
<span class="badge @mStatusBadge"><i class="bi @mStatusIcon me-1"></i>@item.StatusDisplay</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -44,6 +44,91 @@
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var row in Model.Rows)
|
||||
{
|
||||
var oPct = row.TotalSteps == 0 ? 0 : row.StepsCompleted * 100 / row.TotalSteps;
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", "Companies", new { id = row.CompanyId })'">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);">
|
||||
<i class="bi bi-building"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@row.CompanyName</h6>
|
||||
<small>
|
||||
@switch (row.Status)
|
||||
{
|
||||
case OnboardingStatus.Complete:
|
||||
<span class="badge bg-success">Complete</span>
|
||||
break;
|
||||
case OnboardingStatus.InProgress:
|
||||
<span class="badge bg-warning text-dark">In Progress</span>
|
||||
break;
|
||||
case OnboardingStatus.Dismissed:
|
||||
<span class="badge bg-secondary">Dismissed</span>
|
||||
break;
|
||||
default:
|
||||
<span class="badge bg-light text-muted border">Not Started</span>
|
||||
break;
|
||||
}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Wizard</span>
|
||||
<span class="mobile-card-value">
|
||||
@if (row.WizardCompleted)
|
||||
{
|
||||
<i class="bi bi-check-circle-fill text-success"></i>
|
||||
<span class="text-success ms-1">Done</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="bi bi-circle text-muted"></i>
|
||||
<span class="text-muted ms-1">Pending</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Milestones</span>
|
||||
<span class="mobile-card-value">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="progress" style="height:5px; width:60px;">
|
||||
<div class="progress-bar @(oPct == 100 ? "bg-success" : "bg-primary")" style="width:@oPct%"></div>
|
||||
</div>
|
||||
<small class="text-muted">@row.StepsCompleted/@row.TotalSteps</small>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
@{
|
||||
var oFirstActivity = row.FirstJobCreatedAt ?? row.FirstQuoteCreatedAt;
|
||||
}
|
||||
@if (oFirstActivity.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">First Activity</span>
|
||||
<span class="mobile-card-value text-muted">@oFirstActivity.Value.ToString("MMM d, yyyy")</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-controller="Companies" asp-action="Details" asp-route-id="@row.CompanyId" class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation()">
|
||||
<i class="bi bi-building me-1"></i>View
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!Model.Rows.Any())
|
||||
{
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="bi bi-building fs-1 d-block mb-2 opacity-25"></i>
|
||||
No companies found.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0" id="onboardingTable">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -164,6 +164,56 @@
|
||||
<!-- Grid -->
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var po in Model.Items)
|
||||
{
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = po.Id })'">
|
||||
<div class="mobile-card-header" style="@(po.IsOverdue ? "background:#fee2e2;" : "")">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);">
|
||||
<i class="bi bi-cart-check"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@po.PoNumber @(po.IsOverdue ? " — Overdue" : "")</h6>
|
||||
<small>@po.VendorName</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-@StatusBadge(po.Status)">@po.Status</span></span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Order Date</span>
|
||||
<span class="mobile-card-value">@po.OrderDate.ToString("MM/dd/yy")</span>
|
||||
</div>
|
||||
@if (po.ExpectedDeliveryDate.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Expected</span>
|
||||
<span class="mobile-card-value @(po.IsOverdue ? "text-danger fw-semibold" : "")">
|
||||
@po.ExpectedDeliveryDate.Value.ToString("MM/dd/yy")
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Items</span>
|
||||
<span class="mobile-card-value">@po.ItemCount</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Total</span>
|
||||
<span class="mobile-card-value fw-semibold">$@po.TotalAmount.ToString("N2")</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Details" asp-route-id="@po.Id" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
|
||||
<i class="bi bi-eye me-1"></i>View
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -38,6 +38,96 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var t in Model)
|
||||
{
|
||||
var isOverdueRT = t.IsActive && t.NextFireDate.Date < DateTime.Today;
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);">
|
||||
<i class="bi bi-arrow-repeat"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@t.Name</h6>
|
||||
<small>
|
||||
@if (t.TemplateType == RecurringTemplateType.Bill)
|
||||
{
|
||||
<span>Bill</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Expense</span>
|
||||
}
|
||||
—
|
||||
@(t.IntervalCount == 1 ? t.Frequency.ToString() : $"Every {t.IntervalCount} × {t.Frequency}")
|
||||
</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">
|
||||
@if (t.IsActive)
|
||||
{
|
||||
<span class="badge bg-success"><i class="bi bi-play-fill me-1"></i>Active</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary"><i class="bi bi-pause-fill me-1"></i>Paused</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@if (t.IsActive)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Next Fire</span>
|
||||
<span class="mobile-card-value @(isOverdueRT ? "text-danger fw-semibold" : "")">
|
||||
@t.NextFireDate.ToString("MM/dd/yyyy")
|
||||
@if (isOverdueRT) { <i class="bi bi-exclamation-circle ms-1"></i> }
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Occurrences</span>
|
||||
<span class="mobile-card-value">
|
||||
@t.OccurrenceCount
|
||||
@if (t.MaxOccurrences.HasValue) { <span class="text-muted"> / @t.MaxOccurrences</span> }
|
||||
</span>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(t.LastError))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Error</span>
|
||||
<span class="mobile-card-value text-danger small">@t.LastError</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Edit" asp-route-id="@t.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form asp-action="ToggleActive" asp-route-id="@t.Id" method="post" style="display:inline;">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-sm @(t.IsActive ? "btn-outline-warning" : "btn-outline-success")">
|
||||
<i class="bi @(t.IsActive ? "bi-pause" : "bi-play")"></i>
|
||||
</button>
|
||||
</form>
|
||||
@if (t.IsActive)
|
||||
{
|
||||
<form asp-action="GenerateNow" asp-route-id="@t.Id" method="post" style="display:inline;"
|
||||
onsubmit="return confirm('Generate one occurrence now?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-lightning-charge"></i>
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -65,6 +65,77 @@
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var note in Model)
|
||||
{
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #4f46e5 0%, #3730a3 100%);">
|
||||
<i class="bi bi-journal-text"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6><code>v@(note.Version)</code> — @note.Title</h6>
|
||||
<small>
|
||||
<span class="badge @TagBadge(note.Tag)">@note.Tag</span>
|
||||
|
||||
@if (note.IsPublished)
|
||||
{
|
||||
<span class="badge bg-success">Published</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-warning text-dark">Draft</span>
|
||||
}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Released</span>
|
||||
<span class="mobile-card-value text-muted">@note.ReleasedAt.ToString("MM/dd/yyyy")</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Created By</span>
|
||||
<span class="mobile-card-value text-muted">@note.CreatedByUserName</span>
|
||||
</div>
|
||||
@if (note.Body.Length > 0)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Preview</span>
|
||||
<span class="mobile-card-value text-muted">@(note.Body.Length > 60 ? note.Body[..60] + "…" : note.Body)</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Edit" asp-route-id="@note.Id" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<form asp-action="TogglePublish" asp-route-id="@note.Id" method="post" class="d-inline">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-sm @(note.IsPublished ? "btn-outline-warning" : "btn-outline-success")">
|
||||
<i class="bi @(note.IsPublished ? "bi-eye-slash" : "bi-eye")"></i>
|
||||
</button>
|
||||
</form>
|
||||
<form asp-action="Delete" asp-route-id="@note.Id" method="post" class="d-inline"
|
||||
onsubmit="return confirm('Delete v@(note.Version)?')">
|
||||
@Html.AntiForgeryToken()
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!Model.Any())
|
||||
{
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="bi bi-journal-x fs-1 d-block mb-2 opacity-25"></i>
|
||||
No release notes yet. <a asp-action="Create">Create the first one.</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0 small">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -112,6 +112,101 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var row in Model)
|
||||
{
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);">
|
||||
<i class="bi bi-building"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>
|
||||
@row.CompanyName
|
||||
@if (row.IsDeleted) { <span class="badge bg-secondary ms-1">Deleted</span> }
|
||||
</h6>
|
||||
<small>
|
||||
@if (row.SmsDisabledByAdmin)
|
||||
{
|
||||
<span class="text-danger"><i class="bi bi-slash-circle me-1"></i>Admin-Disabled</span>
|
||||
}
|
||||
else if (row.SmsEnabled)
|
||||
{
|
||||
<span class="text-success"><i class="bi bi-chat-dots me-1"></i>SMS Enabled</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">SMS Off</span>
|
||||
}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Terms</span>
|
||||
<span class="mobile-card-value">
|
||||
@{
|
||||
var dispAgreement = row.CurrentAgreement ?? row.LatestAgreement;
|
||||
}
|
||||
@if (row.CurrentAgreement != null)
|
||||
{
|
||||
<span class="badge bg-success">v@(row.CurrentAgreement.TermsVersion)</span>
|
||||
}
|
||||
else if (row.LatestAgreement != null)
|
||||
{
|
||||
<span class="badge bg-warning text-dark">Stale (v@(row.LatestAgreement.TermsVersion))</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-light text-muted border">Never</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@if (dispAgreement != null)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Accepted By</span>
|
||||
<span class="mobile-card-value @(row.CurrentAgreement == null ? "text-muted" : "")">
|
||||
@dispAgreement.AgreedByUserName
|
||||
</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Accepted At</span>
|
||||
<span class="mobile-card-value @(row.CurrentAgreement == null ? "text-muted" : "")">
|
||||
@dispAgreement.AgreedAt.ToString("MM/dd/yy")
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (row.AllAgreements.Count > 0)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">History</span>
|
||||
<span class="mobile-card-value">
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#historyModal"
|
||||
data-company="@row.CompanyName"
|
||||
data-history="@System.Text.Json.JsonSerializer.Serialize(row.AllAgreements.Select(a => new {
|
||||
a.TermsVersion,
|
||||
a.AgreedByUserName,
|
||||
a.AgreedByUserId,
|
||||
AgreedAt = a.AgreedAt.ToString("MMM d, yyyy 'at' h:mm tt") + " UTC",
|
||||
IpAddress = a.IpAddress ?? "—",
|
||||
UserAgent = a.UserAgent ?? "—"
|
||||
}), new System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase })"
|
||||
onclick="event.stopPropagation()">
|
||||
@row.AllAgreements.Count <i class="bi bi-clock-history ms-1"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -110,6 +110,64 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var row in Model.Rows)
|
||||
{
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", "Customers", new { id = row.CustomerId })'">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #ec4899 0%, #be185d 100%);">
|
||||
<i class="bi bi-phone-vibrate"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@row.CustomerName</h6>
|
||||
<small>@(row.MobilePhone ?? row.Phone ?? "No phone")</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">SMS Status</span>
|
||||
<span class="mobile-card-value"><span class="badge @row.StatusBadgeClass">@row.StatusLabel</span></span>
|
||||
</div>
|
||||
@if (row.ConsentedAt.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Consented</span>
|
||||
<span class="mobile-card-value">@row.ConsentedAt.Value.ToString("MMM d, yyyy")</span>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(row.ConsentMethod))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Method</span>
|
||||
<span class="mobile-card-value">@row.ConsentMethod</span>
|
||||
</div>
|
||||
}
|
||||
@if (row.OptedOutAt.HasValue)
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Opted Out</span>
|
||||
<span class="mobile-card-value text-danger">@row.OptedOutAt.Value.ToString("MMM d, yyyy")</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-controller="Customers" asp-action="Details" asp-route-id="@row.CustomerId"
|
||||
class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
|
||||
<i class="bi bi-person me-1"></i>Customer
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!Model.Rows.Any())
|
||||
{
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="bi bi-phone-vibrate fs-1 d-block mb-2 opacity-25"></i>
|
||||
No customers match the current filter.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -84,6 +84,46 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var file in Model.Files.OrderBy(f => f.Status).ThenBy(f => f.RelativePath))
|
||||
{
|
||||
var fStatusBadge = file.Status switch
|
||||
{
|
||||
MigrationFileStatus.Migrated => "bg-success",
|
||||
MigrationFileStatus.Skipped => "bg-secondary",
|
||||
_ => "bg-danger"
|
||||
};
|
||||
var fStatusLabel = file.Status switch
|
||||
{
|
||||
MigrationFileStatus.Migrated => "Migrated",
|
||||
MigrationFileStatus.Skipped => "Already in Azure",
|
||||
_ => "Failed"
|
||||
};
|
||||
<div class="mobile-data-card">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #0369a1 0%, #075985 100%);">
|
||||
<i class="bi bi-file-earmark"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6 class="font-monospace" style="font-size:.75rem;">@file.RelativePath</h6>
|
||||
<small><span class="badge bg-light text-dark border">@file.Container</span></small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-card-body">
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Size</span>
|
||||
<span class="mobile-card-value text-muted">@FormatBytes(file.FileSize)</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Status</span>
|
||||
<span class="mobile-card-value"><span class="badge @fStatusBadge">@fStatusLabel</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead>
|
||||
|
||||
@@ -68,6 +68,64 @@
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="mobile-card-view">
|
||||
<div class="mobile-card-list">
|
||||
@foreach (var vc in Model)
|
||||
{
|
||||
var (vcBadge, vcLabel) = vc.Status switch
|
||||
{
|
||||
VendorCreditStatus.Open => ("bg-success", "Open"),
|
||||
VendorCreditStatus.PartiallyApplied => ("bg-warning text-dark", "Partial"),
|
||||
VendorCreditStatus.Applied => ("bg-secondary", "Applied"),
|
||||
VendorCreditStatus.Voided => ("bg-danger", "Voided"),
|
||||
_ => ("bg-secondary", vc.Status.ToString())
|
||||
};
|
||||
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = vc.Id })'">
|
||||
<div class="mobile-card-header">
|
||||
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">
|
||||
<i class="bi bi-credit-card"></i>
|
||||
</div>
|
||||
<div class="mobile-card-title">
|
||||
<h6>@vc.CreditNumber</h6>
|
||||
<small>@vc.Vendor?.CompanyName</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 @vcBadge">@vcLabel</span></span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Date</span>
|
||||
<span class="mobile-card-value">@vc.CreditDate.ToString("MM/dd/yy")</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Total</span>
|
||||
<span class="mobile-card-value">@vc.Total.ToString("C")</span>
|
||||
</div>
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Remaining</span>
|
||||
<span class="mobile-card-value @(vc.RemainingAmount > 0 ? "text-success fw-semibold" : "text-muted")">
|
||||
@(vc.RemainingAmount > 0 ? vc.RemainingAmount.ToString("C") : "—")
|
||||
</span>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(vc.Memo))
|
||||
{
|
||||
<div class="mobile-card-row">
|
||||
<span class="mobile-card-label">Memo</span>
|
||||
<span class="mobile-card-value">@vc.Memo</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mobile-card-footer">
|
||||
<a asp-action="Details" asp-route-id="@vc.Id" class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation()">
|
||||
View
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
/* Hide desktop table view on mobile */
|
||||
.table-responsive {
|
||||
/* Hide desktop table only when a mobile card view sibling is present */
|
||||
.mobile-card-view ~ .table-responsive,
|
||||
.table-responsive:has(~ .mobile-card-view) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user