Initial commit
This commit is contained in:
@@ -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>
|
||||
}
|
||||
Reference in New Issue
Block a user