Initial commit

This commit is contained in:
2026-04-23 21:38:24 -04:00
commit 63e12a9636
1762 changed files with 1672620 additions and 0 deletions
@@ -0,0 +1,217 @@
@model PowderCoating.Application.DTOs.BugReport.EditBugReportDto
@using PowderCoating.Core.Enums
@using PowderCoating.Application.DTOs.BugReport
@{
ViewData["Title"] = "Edit Bug Report";
ViewData["PageIcon"] = "bi-bug";
}
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<p class="text-muted mb-0">
Submitted by <strong>@Model.SubmittedByUserName</strong> on @Model.CreatedAt.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd/yyyy h:mm tt")
@if (!string.IsNullOrWhiteSpace(Model.CompanyName))
{
<span class="ms-2"><i class="bi bi-building"></i> <strong>@Model.CompanyName</strong></span>
}
</p>
</div>
<a asp-action="Index" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Back to Bug Reports
</a>
</div>
@if (TempData["ErrorMessage"] != null)
{
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle"></i> @TempData["ErrorMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-pencil-square"></i> Report Details</h5>
</div>
<div class="card-body">
<form asp-action="Edit" asp-route-id="@Model.Id" method="post">
<input type="hidden" asp-for="Id" />
<input type="hidden" asp-for="SubmittedByUserName" />
<input type="hidden" asp-for="CompanyName" />
<input type="hidden" asp-for="CreatedAt" />
<input type="hidden" asp-for="CompanyId" />
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
<div class="mb-3">
<label asp-for="Title" class="form-label fw-semibold">
Title <span class="text-danger">*</span>
</label>
<input asp-for="Title" class="form-control" maxlength="200" />
<span asp-validation-for="Title" class="text-danger small"></span>
</div>
<div class="mb-3">
<label asp-for="Description" class="form-label fw-semibold">
Description <span class="text-danger">*</span>
</label>
<textarea asp-for="Description" class="form-control" rows="6" maxlength="4000"></textarea>
<span asp-validation-for="Description" class="text-danger small"></span>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label asp-for="Priority" class="form-label fw-semibold">Priority</label>
<select asp-for="Priority" class="form-select">
@foreach (var p in Enum.GetValues<BugReportPriority>())
{
<option value="@((int)p)">@p</option>
}
</select>
<span asp-validation-for="Priority" class="text-danger small"></span>
</div>
<div class="col-md-6">
<label asp-for="Status" class="form-label fw-semibold">Status</label>
<select asp-for="Status" class="form-select">
<option value="@((int)BugReportStatus.New)">New</option>
<option value="@((int)BugReportStatus.InProgress)">In Progress</option>
<option value="@((int)BugReportStatus.Completed)">Completed</option>
<option value="@((int)BugReportStatus.Cancelled)">Cancelled</option>
</select>
<span asp-validation-for="Status" class="text-danger small"></span>
</div>
</div>
<div class="mb-4">
<label asp-for="ResolutionNotes" class="form-label fw-semibold">Resolution Notes</label>
<textarea asp-for="ResolutionNotes" class="form-control" rows="4" maxlength="4000"
placeholder="Describe the resolution or any notes about this report..."></textarea>
<span asp-validation-for="ResolutionNotes" class="text-danger small"></span>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check-lg"></i> Save Changes
</button>
<a asp-action="Index" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
@if (Model.Attachments.Count > 0)
{
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-paperclip"></i> Attachments (@Model.Attachments.Count)</h5>
</div>
<div class="card-body">
<div class="d-flex flex-wrap gap-3">
@foreach (var att in Model.Attachments)
{
var attUrl = Url.Action("Attachment", "BugReport", new { id = att.Id });
var isImage = att.ContentType.StartsWith("image/");
var isVideo = att.ContentType.StartsWith("video/");
var sizeMb = (att.FileSizeBytes / 1024.0 / 1024.0).ToString("F1");
if (isImage)
{
<div class="attachment-thumb" title="@att.FileName (@sizeMb MB)"
data-type="image" data-src="@attUrl" data-name="@att.FileName"
style="cursor:pointer;">
<img src="@attUrl" alt="@att.FileName"
style="width:120px;height:90px;object-fit:cover;border-radius:6px;border:2px solid #dee2e6;" />
<div class="small text-muted text-truncate mt-1" style="max-width:120px;">@att.FileName</div>
</div>
}
else if (isVideo)
{
<div class="attachment-thumb" title="@att.FileName (@sizeMb MB)"
data-type="video" data-src="@attUrl" data-name="@att.FileName"
data-content-type="@att.ContentType"
style="cursor:pointer;">
<div style="width:120px;height:90px;border-radius:6px;border:2px solid #dee2e6;background:#1a1a2e;display:flex;align-items:center;justify-content:center;">
<i class="bi bi-play-circle-fill text-white" style="font-size:2.5rem;"></i>
</div>
<div class="small text-muted text-truncate mt-1" style="max-width:120px;">@att.FileName</div>
</div>
}
else
{
<div class="d-flex align-items-center gap-2 border rounded p-2" style="min-width:200px;">
<i class="bi bi-file-earmark fs-4 text-secondary"></i>
<div>
<div class="small fw-semibold text-truncate" style="max-width:150px;">@att.FileName</div>
<div class="small text-muted">@sizeMb MB</div>
</div>
<a href="@attUrl" class="btn btn-sm btn-outline-secondary ms-auto" target="_blank">
<i class="bi bi-download"></i>
</a>
</div>
}
}
</div>
</div>
</div>
<!-- Lightbox modal -->
<div class="modal fade" id="attachmentModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content bg-dark">
<div class="modal-header border-secondary py-2">
<h6 class="modal-title text-white mb-0" id="attachmentModalLabel"></h6>
<div class="d-flex gap-2 ms-3">
<a id="attachmentDownloadLink" href="#" class="btn btn-sm btn-outline-light" target="_blank">
<i class="bi bi-download"></i> Download
</a>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
</div>
<div class="modal-body p-2 text-center" id="attachmentModalBody">
</div>
</div>
</div>
</div>
}
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
@if (Model.Attachments.Any(a => a.ContentType.StartsWith("image/") || a.ContentType.StartsWith("video/")))
{
<script>
document.querySelectorAll('.attachment-thumb').forEach(el => {
el.addEventListener('click', function () {
const src = this.dataset.src;
const name = this.dataset.name;
const type = this.dataset.type;
const contentType = this.dataset.contentType || '';
document.getElementById('attachmentModalLabel').textContent = name;
document.getElementById('attachmentDownloadLink').href = src;
const body = document.getElementById('attachmentModalBody');
if (type === 'image') {
body.innerHTML = `<img src="${src}" alt="${name}" style="max-width:100%;max-height:80vh;border-radius:4px;" />`;
} else {
body.innerHTML = `<video controls autoplay style="max-width:100%;max-height:80vh;border-radius:4px;">
<source src="${src}" type="${contentType}">
Your browser does not support video playback.
</video>`;
}
new bootstrap.Modal(document.getElementById('attachmentModal')).show();
});
});
// Stop video playback when modal closes
document.getElementById('attachmentModal')?.addEventListener('hidden.bs.modal', function () {
document.getElementById('attachmentModalBody').innerHTML = '';
});
</script>
}
}
@@ -0,0 +1,364 @@
@model List<PowderCoating.Application.DTOs.BugReport.BugReportDto>
@using PowderCoating.Core.Enums
@{
ViewData["Title"] = "Bug Reports";
ViewData["PageIcon"] = "bi-bug";
var sortCol = ViewBag.SortColumn as string ?? "CreatedAt";
var sortDir = ViewBag.SortDirection as string ?? "desc";
int totalCount = ViewBag.TotalCount ?? 0;
int pageNumber = ViewBag.PageNumber ?? 1;
int pageSize = ViewBag.PageSize ?? 25;
int totalPages = ViewBag.TotalPages ?? 1;
string NextDir(string col) => sortCol == col && sortDir == "asc" ? "desc" : "asc";
string SortIcon(string col) => sortCol == col
? (sortDir == "asc" ? "bi-sort-up" : "bi-sort-down")
: "bi-arrow-down-up text-muted";
}
@section Styles {
<style>
[data-bs-theme="dark"] .table-light th,
[data-bs-theme="dark"] .table-light td {
background-color: var(--bs-secondary-bg);
color: var(--bs-body-color);
}
[data-bs-theme="dark"] .card {
border-color: var(--bs-border-color) !important;
}
[data-bs-theme="dark"] .sort-link {
color: var(--bs-body-color) !important;
}
[data-bs-theme="dark"] .border-top {
border-color: var(--bs-border-color) !important;
}
[data-bs-theme="dark"] .pagination .page-link {
background-color: var(--bs-body-bg);
border-color: var(--bs-border-color);
color: var(--bs-body-color);
}
[data-bs-theme="dark"] .pagination .page-item.active .page-link {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
color: #fff;
}
</style>
}
<div class="container-fluid">
<div class="mb-4"></div>
@if (TempData["SuccessMessage"] != null)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle"></i> @TempData["SuccessMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
@if (TempData["ErrorMessage"] != null)
{
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle"></i> @TempData["ErrorMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
<!-- Filters -->
<div class="card mb-3">
<div class="card-body py-2">
<form method="get" class="row g-2 align-items-end">
<div class="col-md-4">
<label class="form-label small mb-1">Search</label>
<input type="text" name="searchTerm" value="@ViewBag.SearchTerm" class="form-control form-control-sm"
placeholder="Title, description, or submitter..." />
</div>
<div class="col-md-2">
<label class="form-label small mb-1">Status</label>
<select name="statusFilter" class="form-select form-select-sm">
<option value="">All Statuses</option>
@foreach (var s in Enum.GetValues<BugReportStatus>())
{
<option value="@s" selected="@(ViewBag.StatusFilter == s.ToString())">@s.ToString()</option>
}
</select>
</div>
<div class="col-md-2">
<label class="form-label small mb-1">Priority</label>
<select name="priorityFilter" class="form-select form-select-sm">
<option value="">All Priorities</option>
@foreach (var p in Enum.GetValues<BugReportPriority>())
{
<option value="@p" selected="@(ViewBag.PriorityFilter == p.ToString())">@p.ToString()</option>
}
</select>
</div>
<div class="col-md-2">
<label class="form-label small mb-1">Per page</label>
<select name="pageSize" class="form-select form-select-sm">
@foreach (var n in new[] { 10, 25, 50, 100 })
{
<option value="@n" selected="@(pageSize == n)">@n</option>
}
</select>
</div>
<input type="hidden" name="sortColumn" value="@sortCol" />
<input type="hidden" name="sortDirection" value="@sortDir" />
<div class="col-md-2">
<button type="submit" class="btn btn-primary btn-sm w-100">
<i class="bi bi-search"></i> Filter
</button>
</div>
</form>
</div>
</div>
<!-- Table -->
<div class="card">
<div class="card-body p-0">
@if (!Model.Any())
{
<div class="text-center py-5 text-muted">
<i class="bi bi-check-circle display-4 d-block mb-2"></i>
<p>No bug reports found.</p>
</div>
}
else
{
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>
<a asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-pageSize="@pageSize"
asp-route-sortColumn="Title"
asp-route-sortDirection="@NextDir("Title")"
class="text-decoration-none sort-link">
Title <i class="bi @SortIcon("Title")"></i>
</a>
</th>
<th>
<a asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-pageSize="@pageSize"
asp-route-sortColumn="Priority"
asp-route-sortDirection="@NextDir("Priority")"
class="text-decoration-none sort-link">
Priority <i class="bi @SortIcon("Priority")"></i>
</a>
</th>
<th>
<a asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-pageSize="@pageSize"
asp-route-sortColumn="Status"
asp-route-sortDirection="@NextDir("Status")"
class="text-decoration-none sort-link">
Status <i class="bi @SortIcon("Status")"></i>
</a>
</th>
<th>
<a asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-pageSize="@pageSize"
asp-route-sortColumn="Submitted"
asp-route-sortDirection="@NextDir("Submitted")"
class="text-decoration-none sort-link">
Submitted By <i class="bi @SortIcon("Submitted")"></i>
</a>
</th>
<th>Company</th>
<th>
<a asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-pageSize="@pageSize"
asp-route-sortColumn="CreatedAt"
asp-route-sortDirection="@NextDir("CreatedAt")"
class="text-decoration-none sort-link">
Submitted <i class="bi @SortIcon("CreatedAt")"></i>
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var report in Model)
{
<tr style="cursor:pointer" onclick="window.location='@Url.Action("Edit", new { id = report.Id })'">
<td>
<div class="fw-semibold">@report.Title</div>
<div class="text-muted small text-truncate" style="max-width:320px;" title="@report.Description">
@report.Description
</div>
</td>
<td>
@{
var priClass = report.Priority switch
{
BugReportPriority.Critical => "bg-danger",
BugReportPriority.High => "bg-warning text-dark",
BugReportPriority.Normal => "bg-primary",
_ => "bg-secondary"
};
}
<span class="badge @priClass">@report.Priority</span>
</td>
<td>
@{
var statusClass = report.Status switch
{
BugReportStatus.New => "bg-info text-dark",
BugReportStatus.InProgress => "bg-warning text-dark",
BugReportStatus.Completed => "bg-success",
BugReportStatus.Cancelled => "bg-secondary",
_ => "bg-secondary"
};
var statusLabel = report.Status switch
{
BugReportStatus.InProgress => "In Progress",
_ => report.Status.ToString()
};
}
<span class="badge @statusClass">@statusLabel</span>
</td>
<td class="small">@report.SubmittedByUserName</td>
<td class="small text-muted">@report.CompanyId</td>
<td class="small text-muted text-nowrap">@report.CreatedAt.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd/yyyy h:mm tt")</td>
<td onclick="event.stopPropagation()">
<a asp-action="Edit" asp-route-id="@report.Id" class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil"></i> Edit
</a>
</td>
</tr>
}
</tbody>
</table>
</div>
<!-- Mobile card view — shown on screens < 992px -->
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var report in Model)
{
var mPriClass = report.Priority switch
{
BugReportPriority.Critical => "bg-danger",
BugReportPriority.High => "bg-warning text-dark",
BugReportPriority.Normal => "bg-primary",
_ => "bg-secondary"
};
var mStatusClass = report.Status switch
{
BugReportStatus.New => "bg-info text-dark",
BugReportStatus.InProgress => "bg-warning text-dark",
BugReportStatus.Completed => "bg-success",
BugReportStatus.Cancelled => "bg-secondary",
_ => "bg-secondary"
};
var mStatusLabel = report.Status switch
{
BugReportStatus.InProgress => "In Progress",
_ => report.Status.ToString()
};
<a href="@Url.Action("Edit", new { id = report.Id })" class="mobile-data-card text-decoration-none">
<div class="mobile-card-header">
<div class="mobile-card-icon bg-danger"><i class="bi bi-bug"></i></div>
<div class="mobile-card-title">
<h6>@report.Title</h6>
<small>@report.CreatedAt.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd/yyyy h:mm tt")</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 @mStatusClass">@mStatusLabel</span></span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Priority</span>
<span class="mobile-card-value"><span class="badge @mPriClass">@report.Priority</span></span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Reporter</span>
<span class="mobile-card-value">@report.SubmittedByUserName</span>
</div>
@if (report.CompanyId > 0)
{
<div class="mobile-card-row">
<span class="mobile-card-label">Company ID</span>
<span class="mobile-card-value">@report.CompanyId</span>
</div>
}
</div>
<div class="mobile-card-footer">
<span class="btn btn-sm btn-outline-primary">Edit →</span>
</div>
</a>
}
</div>
</div>
<!-- Pagination -->
@if (totalPages > 1)
{
<div class="d-flex justify-content-between align-items-center px-3 py-2 border-top">
<small class="text-muted">
Showing @((pageNumber - 1) * pageSize + 1)@(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" asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-sortColumn="@sortCol"
asp-route-sortDirection="@sortDir"
asp-route-pageSize="@pageSize"
asp-route-pageNumber="@(pageNumber - 1)">
<i class="bi bi-chevron-left"></i>
</a>
</li>
@for (int i = Math.Max(1, pageNumber - 2); i <= Math.Min(totalPages, pageNumber + 2); i++)
{
<li class="page-item @(i == pageNumber ? "active" : "")">
<a class="page-link" asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-sortColumn="@sortCol"
asp-route-sortDirection="@sortDir"
asp-route-pageSize="@pageSize"
asp-route-pageNumber="@i">@i</a>
</li>
}
<li class="page-item @(pageNumber >= totalPages ? "disabled" : "")">
<a class="page-link" asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-sortColumn="@sortCol"
asp-route-sortDirection="@sortDir"
asp-route-pageSize="@pageSize"
asp-route-pageNumber="@(pageNumber + 1)">
<i class="bi bi-chevron-right"></i>
</a>
</li>
</ul>
</nav>
</div>
}
}
</div>
</div>
</div>
@@ -0,0 +1,115 @@
@model PowderCoating.Application.DTOs.BugReport.CreateBugReportDto
@using PowderCoating.Core.Enums
@{
ViewData["Title"] = "Report a Bug";
ViewData["PageIcon"] = "bi-bug";
}
<div class="container-fluid">
<div class="d-flex justify-content-end align-items-center mb-4">
<a asp-controller="Tools" asp-action="Index" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left"></i> Back to Tools
</a>
</div>
@if (TempData["SuccessMessage"] != null)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle"></i> @TempData["SuccessMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
@if (TempData["ErrorMessage"] != null)
{
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle"></i> @TempData["ErrorMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-pencil-square"></i> Bug Report Details</h5>
</div>
<div class="card-body">
<form asp-action="Submit" method="post" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
<div class="mb-3">
<label asp-for="Title" class="form-label fw-semibold">
Title <span class="text-danger">*</span>
</label>
<input asp-for="Title" class="form-control" placeholder="Brief summary of the issue" maxlength="200" />
<span asp-validation-for="Title" class="text-danger small"></span>
<div class="form-text">Provide a short, descriptive title (e.g., "Invoice PDF fails to generate").</div>
</div>
<div class="mb-3">
<label asp-for="Description" class="form-label fw-semibold">
Description <span class="text-danger">*</span>
</label>
<textarea asp-for="Description" class="form-control" rows="7"
placeholder="Describe what happened, what you expected to happen, and any steps to reproduce the issue..." maxlength="4000"></textarea>
<span asp-validation-for="Description" class="text-danger small"></span>
<div class="form-text">Include steps to reproduce, what you expected, and what actually happened.</div>
</div>
<div class="mb-4">
<label asp-for="Priority" class="form-label fw-semibold">Priority</label>
<select asp-for="Priority" class="form-select">
<option value="@((int)BugReportPriority.Low)">Low Minor inconvenience, workaround exists</option>
<option value="@((int)BugReportPriority.Normal)" selected>Normal Affects workflow but not critical</option>
<option value="@((int)BugReportPriority.High)">High Significantly impacts operations</option>
<option value="@((int)BugReportPriority.Critical)">Critical System unusable or data loss risk</option>
</select>
<span asp-validation-for="Priority" class="text-danger small"></span>
</div>
<div class="mb-4">
<label for="attachments" class="form-label fw-semibold">
<i class="bi bi-paperclip"></i> Attachments <span class="text-muted fw-normal">(optional)</span>
</label>
<input type="file" id="attachments" name="attachments" class="form-control"
multiple accept=".jpg,.jpeg,.png,.gif,.webp,.mp4,.mov,.avi,.mkv,.webm"
onchange="updateFileList(this)" />
<div class="form-text">Photos or videos up to 100 MB each. Accepted: JPG, PNG, GIF, WEBP, MP4, MOV, AVI, MKV, WEBM.</div>
<ul id="fileList" class="list-unstyled mt-2 small text-muted"></ul>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
<i class="bi bi-send"></i> Submit Report
</button>
<a asp-controller="Tools" asp-action="Index" class="btn btn-outline-secondary">
Cancel
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script>
function updateFileList(input) {
const list = document.getElementById('fileList');
list.innerHTML = '';
const maxBytes = 100 * 1024 * 1024;
Array.from(input.files).forEach(f => {
const li = document.createElement('li');
const sizeMb = (f.size / 1024 / 1024).toFixed(1);
if (f.size > maxBytes) {
li.innerHTML = `<i class="bi bi-exclamation-triangle text-danger"></i> ${f.name} (${sizeMb} MB) — exceeds 100 MB limit`;
} else {
li.innerHTML = `<i class="bi bi-file-earmark text-secondary"></i> ${f.name} (${sizeMb} MB)`;
}
list.appendChild(li);
});
}
</script>
}