Files
PowderCoatingLogix/src/PowderCoating.Web/Views/AiUsageReport/Index.cshtml
T
spouliot cf6acc125f Complete mobile card view coverage for all remaining pages
- CSS fix: change blanket .table-responsive hide to only trigger when
  a .mobile-card-view sibling exists (.mobile-card-view ~ .table-responsive
  and :has() rule) — auto-fixes 60+ forms/reports/detail/help pages that
  were showing blank on mobile by making their tables scroll instead
- Add mobile card views to remaining list pages:
  JobsPriority (overdue jobs, main board, maintenance sections)
  NotificationLogs (email/SMS log entries)
  AiUsageReport (per-company AI usage breakdown)
  GiftCertificates/BulkResult (batch certificate list)
  Inventory/SamplePanels (Need to Order + On Wall tabs)
  BannedIps (active bans + lifted/expired bans)
  OnboardingProgress (per-company activation funnel)
  ReleaseNotes/Manage (versioned changelog entries)
  StorageMigration/Results (file migration status list)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 23:31:38 -04:00

269 lines
14 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.
@model AiUsageReportViewModel
@{
ViewData["Title"] = "AI Usage Report";
ViewData["PageIcon"] = "bi-robot";
string FeatureIcon(string? f) => f switch {
"PhotoQuote" => "bi-camera",
"HelpChat" => "bi-chat-dots",
"ReceiptScan" => "bi-receipt",
"AccountSuggest" => "bi-tags",
"ArFollowUp" => "bi-envelope",
"FinancialSummary" => "bi-bar-chart",
"CashFlowForecast" => "bi-graph-up",
"AnomalyDetection" => "bi-exclamation-triangle",
_ => "bi-cpu"
};
}
<div class="container-fluid mt-3">
<div class="mb-2">
<a asp-controller="PlatformAdmin" asp-action="Observability" class="text-muted small text-decoration-none">
<i class="bi bi-arrow-left me-1"></i>Observability
</a>
</div>
<div class="d-flex align-items-center justify-content-between mb-4">
<div>
<h4 class="mb-0"><i class="bi bi-robot me-2 text-primary"></i>AI Usage Report</h4>
<p class="text-muted small mb-0">Anthropic API call volume and photo uploads per tenant. Last 30 days unless noted.</p>
</div>
</div>
<!-- Summary Cards -->
<div class="row g-3 mb-4">
<div class="col-sm-6 col-xl-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex align-items-center gap-3">
<div class="rounded-circle bg-primary bg-opacity-10 d-flex align-items-center justify-content-center" style="width:48px;height:48px;flex-shrink:0">
<i class="bi bi-cpu text-primary fs-4"></i>
</div>
<div>
<div class="fs-3 fw-bold">@Model.TotalCallsLast30Days.ToString("N0")</div>
<div class="text-muted small">AI Calls — Last 30 Days</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex align-items-center gap-3">
<div class="rounded-circle bg-success bg-opacity-10 d-flex align-items-center justify-content-center" style="width:48px;height:48px;flex-shrink:0">
<i class="bi bi-activity text-success fs-4"></i>
</div>
<div>
<div class="fs-3 fw-bold">@Model.TotalCallsToday.ToString("N0")</div>
<div class="text-muted small">AI Calls Today</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex align-items-center gap-3">
<div class="rounded-circle bg-info bg-opacity-10 d-flex align-items-center justify-content-center" style="width:48px;height:48px;flex-shrink:0">
<i class="bi bi-camera text-info fs-4"></i>
</div>
<div>
<div class="fs-3 fw-bold">@Model.TotalPhotosUploaded.ToString("N0")</div>
<div class="text-muted small">Total AI Photos Uploaded</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex align-items-center gap-3">
<div class="rounded-circle bg-warning bg-opacity-10 d-flex align-items-center justify-content-center" style="width:48px;height:48px;flex-shrink:0">
<i class="bi bi-building text-warning fs-4"></i>
</div>
<div>
<div class="fs-3 fw-bold">@Model.CompaniesActiveToday</div>
<div class="text-muted small">Companies Active Today</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tier Legend -->
<div class="d-flex flex-wrap gap-2 mb-3 align-items-center">
<span class="text-muted small fw-semibold me-1">Usage Tier (last 30 days):</span>
<span class="badge bg-secondary">Inactive — 0 calls</span>
<span class="badge bg-success">Light — 110</span>
<span class="badge bg-primary">Regular — 1150</span>
<span class="badge bg-warning text-dark">Heavy — 51200</span>
<span class="badge bg-danger">Power User — 200+</span>
</div>
<!-- Main Table -->
<div class="card shadow-sm">
<div class="card-header d-flex align-items-center justify-content-between">
<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>&mdash;</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>&mdash;</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>&mdash;</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">
<tr>
<th>Company</th>
<th>Plan</th>
<th class="text-center" title="Calls today">Today</th>
<th class="text-center" title="Calls in the last 7 days">7 Days</th>
<th class="text-center" title="Calls in the last 30 days">30 Days</th>
<th class="text-center" title="All-time call count">All Time</th>
<th class="text-center" title="Total AI analysis photos uploaded">Photos</th>
<th>Top Feature (30d)</th>
<th class="text-center">Tier</th>
</tr>
</thead>
<tbody>
@foreach (var row in Model.Rows)
{
<tr>
<td>
<a asp-controller="Companies" asp-action="Details" asp-route-id="@row.CompanyId"
class="fw-semibold text-decoration-none">
@row.CompanyName
</a>
@if (!row.IsActive)
{
<span class="badge bg-secondary ms-1">Inactive</span>
}
</td>
<td>
<span class="badge bg-secondary-subtle text-secondary-emphasis border border-secondary-subtle">
@row.Plan
</span>
</td>
<td class="text-center @(row.Today > 0 ? "fw-semibold" : "text-muted")">
@(row.Today > 0 ? row.Today.ToString("N0") : "—")
</td>
<td class="text-center @(row.Last7Days > 0 ? "fw-semibold" : "text-muted")">
@(row.Last7Days > 0 ? row.Last7Days.ToString("N0") : "—")
</td>
<td class="text-center @(row.Last30Days > 0 ? "fw-semibold" : "text-muted")">
@(row.Last30Days > 0 ? row.Last30Days.ToString("N0") : "—")
</td>
<td class="text-center @(row.AllTime > 0 ? "" : "text-muted")">
@(row.AllTime > 0 ? row.AllTime.ToString("N0") : "—")
</td>
<td class="text-center @(row.PhotoCount > 0 ? "" : "text-muted")">
@if (row.PhotoCount > 0)
{
<span><i class="bi bi-camera me-1"></i>@row.PhotoCount.ToString("N0")</span>
}
else
{
<span>—</span>
}
</td>
<td>
@if (row.TopFeature != null)
{
<span title="@string.Join(", ", row.FeatureBreakdown.OrderByDescending(kv => kv.Value).Select(kv => $"{row.FeatureDisplayName(kv.Key)}: {kv.Value}"))">
<i class="bi @FeatureIcon(row.TopFeature) me-1 text-muted"></i>
@row.FeatureDisplayName(row.TopFeature)
@if (row.FeatureBreakdown.Count > 1)
{
<span class="badge bg-light text-muted border ms-1" style="font-size:.7rem">
+@(row.FeatureBreakdown.Count - 1) more
</span>
}
</span>
}
else
{
<span class="text-muted">—</span>
}
</td>
<td class="text-center">
<span class="badge @row.TierBadgeClass">@row.UsageTier</span>
</td>
</tr>
}
@if (!Model.Rows.Any())
{
<tr>
<td colspan="9" 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. Usage data will appear here once tenants start using AI features.
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>