Files
PowderCoatingLogix/src/PowderCoating.Web/Views/Appointments/Index.cshtml
T
spouliot f467862877 Add mobile card views to 12 high-priority list pages
Pages were blank on phones because mobile-cards.css hides .table-responsive
below 992px. Added .mobile-card-view sections to: GiftCertificates, PurchaseOrders,
CreditMemos, VendorCredits, JournalEntries, Appointments, InAppNotifications,
BankReconciliations, FixedAssets, RecurringTemplates, SmsAgreements, SmsConsentAudit.

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

361 lines
19 KiB
Plaintext

@model PagedResult<PowderCoating.Application.DTOs.Appointment.AppointmentListDto>
@{
ViewData["Title"] = "Appointments";
ViewData["PageIcon"] = "bi-calendar-event";
ViewData["PageHelpTitle"] = "Appointments";
ViewData["PageHelpContent"] = "Schedule and track customer visits, drop-offs, pick-ups, consultations, and internal meetings. Appointments can be linked to customers and jobs. Statuses: Scheduled → Confirmed → In Progress → Completed. Use the Calendar view for a visual day/week/month overview.";
}
<!-- Stats Cards - Desktop -->
<div class="stats-cards-desktop">
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<p class="text-muted mb-1" style="font-size: 0.875rem;">Total Appointments</p>
<h3 class="mb-0 fw-bold">@Model.TotalCount</h3>
</div>
<div class="rounded-circle p-3" style="background: #dbeafe;">
<i class="bi bi-calendar-event text-primary" style="font-size: 1.5rem;"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<p class="text-muted mb-1" style="font-size: 0.875rem;">Today</p>
<h3 class="mb-0 fw-bold">@Model.Items.Count(a => a.ScheduledStartTime.Date == DateTime.Today)</h3>
</div>
<div class="rounded-circle p-3" style="background: #fef3c7;">
<i class="bi bi-clock text-warning" style="font-size: 1.5rem;"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<p class="text-muted mb-1" style="font-size: 0.875rem;">This Week</p>
<h3 class="mb-0 fw-bold">@Model.Items.Count(a => a.ScheduledStartTime >= DateTime.Today && a.ScheduledStartTime < DateTime.Today.AddDays(7))</h3>
</div>
<div class="rounded-circle p-3" style="background: #d1fae5;">
<i class="bi bi-calendar-week text-success" style="font-size: 1.5rem;"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<p class="text-muted mb-1" style="font-size: 0.875rem;">Confirmed</p>
<h3 class="mb-0 fw-bold">@Model.Items.Count(a => a.StatusDisplayName == "Confirmed")</h3>
</div>
<div class="rounded-circle p-3" style="background: #e0e7ff;">
<i class="bi bi-check-circle text-info" style="font-size: 1.5rem;"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Compact Stats - Mobile -->
<div class="mobile-stats-compact">
<div class="card">
<div class="stats-grid">
<div class="stat-item">
<div class="stat-icon"><i class="bi bi-calendar-event text-primary"></i></div>
<div class="stat-value">@Model.TotalCount</div>
<div class="stat-label">Total</div>
</div>
<div class="stat-item">
<div class="stat-icon"><i class="bi bi-clock text-warning"></i></div>
<div class="stat-value">@Model.Items.Count(a => a.ScheduledStartTime.Date == DateTime.Today)</div>
<div class="stat-label">Today</div>
</div>
<div class="stat-item">
<div class="stat-icon"><i class="bi bi-calendar-week text-success"></i></div>
<div class="stat-value">@Model.Items.Count(a => a.ScheduledStartTime >= DateTime.Today && a.ScheduledStartTime < DateTime.Today.AddDays(7))</div>
<div class="stat-label">This Week</div>
</div>
<div class="stat-item">
<div class="stat-icon"><i class="bi bi-check-circle text-info"></i></div>
<div class="stat-value">@Model.Items.Count(a => a.StatusDisplayName == "Confirmed")</div>
<div class="stat-label">Confirmed</div>
</div>
</div>
</div>
</div>
<!-- Search and Actions Bar -->
<div class="card mb-3">
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<form method="get" asp-action="Index">
<div class="input-group">
<input type="text" class="form-control" name="searchTerm" value="@ViewBag.SearchTerm" placeholder="Search appointments...">
<input type="hidden" name="sortColumn" value="@ViewBag.SortColumn" />
<input type="hidden" name="sortDirection" value="@ViewBag.SortDirection" />
<input type="hidden" name="pageSize" value="@Model.PageSize" />
<button class="btn btn-outline-secondary" type="submit">
<i class="bi bi-search"></i>
</button>
@if (!string.IsNullOrEmpty(ViewBag.SearchTerm))
{
<a href="@Url.Action("Index")" class="btn btn-outline-secondary">
<i class="bi bi-x"></i>
</a>
}
</div>
</form>
</div>
<div class="col-md-3">
<form method="get" asp-action="Index" id="typeFilterForm">
<input type="hidden" name="searchTerm" value="@ViewBag.SearchTerm" />
<input type="hidden" name="statusFilter" value="@ViewBag.StatusFilter" />
<input type="hidden" name="sortColumn" value="@ViewBag.SortColumn" />
<input type="hidden" name="sortDirection" value="@ViewBag.SortDirection" />
<input type="hidden" name="pageSize" value="@Model.PageSize" />
<select class="form-select" name="typeFilter" onchange="document.getElementById('typeFilterForm').submit()">
<option value="">All Types</option>
@foreach (var item in (SelectList)ViewBag.TypeFilterList)
{
<option value="@item.Value" selected="@(item.Value == ViewBag.TypeFilter?.ToString())">@item.Text</option>
}
</select>
</form>
</div>
<div class="col-md-3">
<form method="get" asp-action="Index" id="statusFilterForm">
<input type="hidden" name="searchTerm" value="@ViewBag.SearchTerm" />
<input type="hidden" name="typeFilter" value="@ViewBag.TypeFilter" />
<input type="hidden" name="sortColumn" value="@ViewBag.SortColumn" />
<input type="hidden" name="sortDirection" value="@ViewBag.SortDirection" />
<input type="hidden" name="pageSize" value="@Model.PageSize" />
<select class="form-select" name="statusFilter" onchange="document.getElementById('statusFilterForm').submit()">
<option value="">All Statuses</option>
@foreach (var item in (SelectList)ViewBag.StatusFilterList)
{
<option value="@item.Value" selected="@(item.Value == ViewBag.StatusFilter?.ToString())">@item.Text</option>
}
</select>
</form>
</div>
<div class="col-md-2 text-end">
<a asp-action="Calendar" class="btn btn-outline-primary w-100 mb-2">
<i class="bi bi-calendar3"></i> View Calendar
</a>
<a asp-action="Create" class="btn btn-primary w-100">
<i class="bi bi-plus-circle"></i> New Appointment
</a>
</div>
</div>
</div>
</div>
<!-- Appointments Table -->
<div class="card">
<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} &ndash; {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>
<tr>
<th sortable-column="AppointmentNumber" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Number</th>
<th sortable-column="Title" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Title</th>
<th sortable-column="Customer" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Customer</th>
<th sortable-column="Type" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Type</th>
<th sortable-column="Status" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Status</th>
<th sortable-column="ScheduledStartTime" current-sort="@ViewBag.SortColumn" current-direction="@ViewBag.SortDirection">Scheduled</th>
<th>Worker</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var appointment in Model.Items)
{
<tr>
<td>
<a asp-action="Details" asp-route-id="@appointment.Id" class="text-decoration-none">
@appointment.AppointmentNumber
</a>
</td>
<td>
<strong>@appointment.Title</strong>
@if (appointment.IsAllDay)
{
<span class="badge bg-secondary ms-1">All Day</span>
}
</td>
<td>@(appointment.CustomerName ?? "<em class=\"text-muted\">Internal</em>")</td>
<td>
<span class="badge bg-@appointment.TypeColorClass">
@appointment.TypeDisplayName
</span>
</td>
<td>
<span class="badge bg-@appointment.StatusColorClass">
@appointment.StatusDisplayName
</span>
</td>
<td>
<div>@appointment.ScheduledStartTime.ToString("MMM dd, yyyy")</div>
@if (!appointment.IsAllDay)
{
<small class="text-muted">@appointment.ScheduledStartTime.ToString("h:mm tt") - @appointment.ScheduledEndTime.ToString("h:mm tt")</small>
}
</td>
<td>
@if (!string.IsNullOrEmpty(appointment.AssignedWorkerName))
{
<span class="badge bg-info">
<i class="bi bi-person"></i> @appointment.AssignedWorkerName
</span>
}
else
{
<span class="text-muted">Unassigned</span>
}
</td>
<td>
<div class="btn-group" role="group">
<a asp-action="Details" asp-route-id="@appointment.Id" class="btn btn-sm btn-outline-primary" title="View Details">
<i class="bi bi-eye"></i>
</a>
<a asp-action="Edit" asp-route-id="@appointment.Id" class="btn btn-sm btn-outline-secondary" title="Edit">
<i class="bi bi-pencil"></i>
</a>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteAppointment(@appointment.Id, '@appointment.AppointmentNumber')" title="Delete">
<i class="bi bi-trash"></i>
</button>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
<!-- Pagination -->
<partial name="_Pagination" model="Model" />
}
else
{
<div class="text-center py-5">
<i class="bi bi-calendar-x text-muted" style="font-size: 3rem;"></i>
<p class="text-muted mt-3">No appointments found.</p>
<a asp-action="Create" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> Create First Appointment
</a>
</div>
}
</div>
</div>
@section Scripts {
<script>
function deleteAppointment(id, appointmentNumber) {
if (confirm(`Are you sure you want to delete appointment ${appointmentNumber}?`)) {
const form = document.createElement('form');
form.method = 'POST';
form.action = '@Url.Action("Delete")';
const idInput = document.createElement('input');
idInput.type = 'hidden';
idInput.name = 'id';
idInput.value = id;
form.appendChild(idInput);
const tokenInput = document.createElement('input');
tokenInput.type = 'hidden';
tokenInput.name = '__RequestVerificationToken';
tokenInput.value = document.querySelector('input[name="__RequestVerificationToken"]')?.value || '';
form.appendChild(tokenInput);
document.body.appendChild(form);
form.submit();
}
}
function changePageSize(pageSize) {
const url = new URL(window.location);
url.searchParams.set('pageSize', pageSize);
url.searchParams.set('pageNumber', '1');
window.location.href = url.toString();
}
</script>
}