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,306 @@
@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="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>
}