feat(frontend): Replace React with Blazor frontend
This commit is contained in:
238
frontend/Components/Pages/Services.razor
Normal file
238
frontend/Components/Pages/Services.razor
Normal file
@@ -0,0 +1,238 @@
|
||||
@page "/services"
|
||||
@using NimbusFlow.Frontend.Services
|
||||
@using NimbusFlow.Frontend.Models
|
||||
@inject IApiService ApiService
|
||||
|
||||
<PageTitle>Services</PageTitle>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Services</h1>
|
||||
<a href="/services/create" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Create Service
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@if (loading)
|
||||
{
|
||||
<div class="text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else if (services.Any())
|
||||
{
|
||||
<!-- Filter Controls -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-4">
|
||||
<select class="form-select" @bind="selectedServiceType">
|
||||
<option value="">All Service Types</option>
|
||||
@foreach (var serviceType in serviceTypes)
|
||||
{
|
||||
<option value="@serviceType.ServiceTypeId">@serviceType.TypeName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<input type="date" class="form-control" @bind="filterDate" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="showPastServices" id="showPast">
|
||||
<label class="form-check-label" for="showPast">
|
||||
Show past services
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Service Type</th>
|
||||
<th>Scheduled Members</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var service in filteredServices.OrderBy(s => s.ServiceDate))
|
||||
{
|
||||
<tr class="@(service.ServiceDate < DateTime.Today ? "text-muted" : "")">
|
||||
<td>
|
||||
<strong>@service.ServiceDate.ToString("MMM dd, yyyy (dddd)")</strong>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge @GetServiceTypeBadgeClass(service.ServiceTypeName)">
|
||||
@service.ServiceTypeName
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@{
|
||||
var serviceSchedules = schedules.Where(s => s.ServiceId == service.ServiceId).ToList();
|
||||
var acceptedCount = serviceSchedules.Count(s => s.Status == "accepted");
|
||||
var pendingCount = serviceSchedules.Count(s => s.Status == "pending");
|
||||
var declinedCount = serviceSchedules.Count(s => s.Status == "declined");
|
||||
}
|
||||
<div class="d-flex gap-1">
|
||||
@if (acceptedCount > 0)
|
||||
{
|
||||
<span class="badge bg-success">@acceptedCount accepted</span>
|
||||
}
|
||||
@if (pendingCount > 0)
|
||||
{
|
||||
<span class="badge bg-warning text-dark">@pendingCount pending</span>
|
||||
}
|
||||
@if (declinedCount > 0)
|
||||
{
|
||||
<span class="badge bg-danger">@declinedCount declined</span>
|
||||
}
|
||||
@if (serviceSchedules.Count == 0)
|
||||
{
|
||||
<span class="text-muted">No schedules</span>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
@if (service.ServiceDate < DateTime.Today)
|
||||
{
|
||||
<span class="badge bg-secondary">Past</span>
|
||||
}
|
||||
else if (service.ServiceDate == DateTime.Today)
|
||||
{
|
||||
<span class="badge bg-primary">Today</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-success">Upcoming</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="/services/@service.ServiceId" class="btn btn-sm btn-outline-primary">View</a>
|
||||
@if (service.ServiceDate >= DateTime.Today)
|
||||
{
|
||||
<a href="/schedules/create?serviceId=@service.ServiceId" class="btn btn-sm btn-success">Schedule</a>
|
||||
<a href="/services/@service.ServiceId/edit" class="btn btn-sm btn-outline-warning">Edit</a>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-12">
|
||||
<small class="text-muted">
|
||||
Showing @filteredServices.Count() of @services.Count services
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-center">
|
||||
<div class="alert alert-info">
|
||||
<h4>No Services Found</h4>
|
||||
<p>There are currently no services in the system.</p>
|
||||
<a href="/services/create" class="btn btn-primary">Create Your First Service</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Service> services = new();
|
||||
private List<ServiceType> serviceTypes = new();
|
||||
private List<Schedule> schedules = new();
|
||||
private bool loading = true;
|
||||
private string selectedServiceType = "";
|
||||
private DateTime? filterDate;
|
||||
private bool showPastServices = false;
|
||||
|
||||
private IEnumerable<Service> filteredServices
|
||||
{
|
||||
get
|
||||
{
|
||||
var filtered = services.AsEnumerable();
|
||||
|
||||
if (!showPastServices)
|
||||
{
|
||||
filtered = filtered.Where(s => s.ServiceDate >= DateTime.Today);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(selectedServiceType) && int.TryParse(selectedServiceType, out int typeId))
|
||||
{
|
||||
filtered = filtered.Where(s => s.ServiceTypeId == typeId);
|
||||
}
|
||||
|
||||
if (filterDate.HasValue)
|
||||
{
|
||||
filtered = filtered.Where(s => s.ServiceDate.Date == filterDate.Value.Date);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
try
|
||||
{
|
||||
loading = true;
|
||||
|
||||
// Load all data in parallel
|
||||
var servicesTask = ApiService.GetServicesAsync();
|
||||
var serviceTypesTask = ApiService.GetServiceTypesAsync();
|
||||
var schedulesTask = ApiService.GetSchedulesAsync();
|
||||
|
||||
await Task.WhenAll(servicesTask, serviceTypesTask, schedulesTask);
|
||||
|
||||
services = servicesTask.Result;
|
||||
serviceTypes = serviceTypesTask.Result;
|
||||
schedules = schedulesTask.Result;
|
||||
|
||||
// Map service type names to services
|
||||
foreach (var service in services)
|
||||
{
|
||||
var serviceType = serviceTypes.FirstOrDefault(st => st.ServiceTypeId == service.ServiceTypeId);
|
||||
service.ServiceTypeName = serviceType?.TypeName ?? "Unknown";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error loading services data: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetServiceTypeBadgeClass(string? serviceTypeName)
|
||||
{
|
||||
return serviceTypeName switch
|
||||
{
|
||||
"9AM" => "bg-info",
|
||||
"11AM" => "bg-primary",
|
||||
"6PM" => "bg-dark",
|
||||
_ => "bg-secondary"
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user