337 lines
12 KiB
C#
337 lines
12 KiB
C#
using System.Text.Json;
|
|
#if POSTGRES
|
|
using Events.EF.Data.Postgres;
|
|
#else
|
|
using Events.EF.Data.MSSQL;
|
|
#endif
|
|
using Events.EF.Models;
|
|
using Events.MVC.Models;
|
|
using Events.MVC.Models.People;
|
|
using Events.MVC.Util.Extensions;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Options;
|
|
using Sieve.Models;
|
|
using Sieve.Services;
|
|
|
|
namespace Events.MVC.Controllers;
|
|
|
|
public class PeopleController : Controller
|
|
{
|
|
private readonly EventsContext ctx;
|
|
private readonly ISieveProcessor sieveProcessor;
|
|
private readonly PagingSettings pagingSettings;
|
|
|
|
public PeopleController(EventsContext ctx, ISieveProcessor sieveProcessor, IOptionsSnapshot<PagingSettings> pagingSettings)
|
|
{
|
|
this.ctx = ctx;
|
|
this.sieveProcessor = sieveProcessor;
|
|
this.pagingSettings = pagingSettings.Value;
|
|
}
|
|
|
|
public async Task<IActionResult> Index(SieveModel sieveModel)
|
|
{
|
|
if (!await ctx.Countries.AsNoTracking().AnyAsync())
|
|
{
|
|
TempData[Constants.TempDataKeys.ToastVariant] = Constants.ToastVariants.Error;
|
|
TempData[Constants.TempDataKeys.ToastTitle] = Constants.ToastTitles.Error;
|
|
TempData[Constants.TempDataKeys.ToastMessage] = Constants.Messages.CountriesRequiredForPeople;
|
|
return RedirectToAction("Index", "Countries");
|
|
}
|
|
|
|
await PopulatePeopleCountryFilterViewDataAsync(sieveModel.Filters);
|
|
var viewModel = await BuildPeopleListAsync(sieveModel);
|
|
if (Request.Headers.ContainsKey(Constants.HtmxHeaders.Request))
|
|
{
|
|
return PartialView("_PeopleList", viewModel);
|
|
}
|
|
|
|
ViewData[Constants.ViewDataKeys.CreatePersonModel] = new PersonViewModel
|
|
{
|
|
CountryOptions = await GetCountryOptionsAsync()
|
|
};
|
|
return View(viewModel);
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<IActionResult> Row(int id)
|
|
{
|
|
var person = await ctx.People
|
|
.Select(p => new PersonViewModel
|
|
{
|
|
Id = p.Id,
|
|
FirstName = p.FirstName,
|
|
LastName = p.LastName,
|
|
FirstNameTranscription = p.FirstNameTranscription,
|
|
LastNameTranscription = p.LastNameTranscription,
|
|
FullName = p.FirstName + " " + p.LastName,
|
|
FullNameTranscription = p.FirstNameTranscription + " " + p.LastNameTranscription,
|
|
BirthDate = p.BirthDate,
|
|
CountryName = p.CountryCodeNavigation.Name,
|
|
RegistrationsCount = p.Registrations.Count
|
|
})
|
|
.FirstOrDefaultAsync(p => p.Id == id);
|
|
|
|
if (person is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
return PartialView("_PersonRow", person);
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<IActionResult> EditRow(int id)
|
|
{
|
|
var person = await ctx.People
|
|
.Select(p => new PersonViewModel
|
|
{
|
|
Id = p.Id,
|
|
FirstName = p.FirstName,
|
|
LastName = p.LastName,
|
|
FirstNameTranscription = p.FirstNameTranscription,
|
|
LastNameTranscription = p.LastNameTranscription,
|
|
AddressLine = p.AddressLine,
|
|
PostalCode = p.PostalCode,
|
|
City = p.City,
|
|
AddressCountry = p.AddressCountry,
|
|
Email = p.Email,
|
|
ContactPhone = p.ContactPhone,
|
|
BirthDate = p.BirthDate,
|
|
DocumentNumber = p.DocumentNumber,
|
|
CountryCode = p.CountryCode
|
|
})
|
|
.FirstOrDefaultAsync(p => p.Id == id);
|
|
|
|
if (person is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
person.CountryOptions = await GetCountryOptionsAsync(person.CountryCode);
|
|
return PartialView("_PersonEditRow", person);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Create(PersonViewModel model, SieveModel sieveModel)
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
model.CountryOptions = await GetCountryOptionsAsync(model.CountryCode);
|
|
Response.Headers[Constants.HtmxHeaders.Retarget] = "#create-person-form";
|
|
Response.Headers[Constants.HtmxHeaders.Reswap] = Constants.HtmxSwap.OuterHtml;
|
|
return PartialView("_CreatePersonForm", model);
|
|
}
|
|
|
|
var person = new Person
|
|
{
|
|
FirstName = model.FirstName.TrimToNull(),
|
|
LastName = model.LastName.TrimToNull(),
|
|
FirstNameTranscription = model.FirstNameTranscription.Trim(),
|
|
LastNameTranscription = model.LastNameTranscription.Trim(),
|
|
AddressLine = model.AddressLine.TrimToNull(),
|
|
PostalCode = model.PostalCode.TrimToNull(),
|
|
City = model.City.TrimToNull(),
|
|
AddressCountry = model.AddressCountry.TrimToNull(),
|
|
Email = model.Email.TrimToNull(),
|
|
ContactPhone = model.ContactPhone.TrimToNull(),
|
|
BirthDate = model.BirthDate!.Value,
|
|
DocumentNumber = model.DocumentNumber.Trim(),
|
|
CountryCode = model.CountryCode.Trim().ToUpperInvariant()
|
|
};
|
|
|
|
ctx.People.Add(person);
|
|
await ctx.SaveChangesAsync();
|
|
|
|
Response.Headers[Constants.HtmxHeaders.Trigger] = JsonSerializer.Serialize(new Dictionary<string, object?>
|
|
{
|
|
[Constants.HtmxEvents.PersonCreated] = true,
|
|
[Constants.HtmxEvents.ShowToast] = new
|
|
{
|
|
variant = Constants.ToastVariants.Success,
|
|
title = Constants.ToastTitles.Success,
|
|
message = $"Person '{person.FirstName} {person.LastName}' was added successfully."
|
|
}
|
|
});
|
|
|
|
await PopulatePeopleCountryFilterViewDataAsync(sieveModel.Filters);
|
|
var viewModel = await BuildPeopleListAsync(sieveModel);
|
|
return PartialView("_PeopleList", viewModel);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Edit(int id, PersonViewModel model)
|
|
{
|
|
if (id != model.Id)
|
|
{
|
|
return BadRequest();
|
|
}
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
model.CountryOptions = await GetCountryOptionsAsync(model.CountryCode);
|
|
return PartialView("_PersonEditRow", model);
|
|
}
|
|
|
|
var person = await ctx.People.FirstOrDefaultAsync(p => p.Id == id);
|
|
if (person is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
person.FirstName = model.FirstName.TrimToNull();
|
|
person.LastName = model.LastName.TrimToNull();
|
|
person.FirstNameTranscription = model.FirstNameTranscription.Trim();
|
|
person.LastNameTranscription = model.LastNameTranscription.Trim();
|
|
person.AddressLine = model.AddressLine.TrimToNull();
|
|
person.PostalCode = model.PostalCode.TrimToNull();
|
|
person.City = model.City.TrimToNull();
|
|
person.AddressCountry = model.AddressCountry.TrimToNull();
|
|
person.Email = model.Email.TrimToNull();
|
|
person.ContactPhone = model.ContactPhone.TrimToNull();
|
|
person.BirthDate = model.BirthDate!.Value;
|
|
person.DocumentNumber = model.DocumentNumber.Trim();
|
|
person.CountryCode = model.CountryCode.Trim().ToUpperInvariant();
|
|
await ctx.SaveChangesAsync();
|
|
|
|
var rowModel = await ctx.People
|
|
.Where(p => p.Id == id)
|
|
.Select(p => new PersonViewModel
|
|
{
|
|
Id = p.Id,
|
|
FirstName = p.FirstName,
|
|
LastName = p.LastName,
|
|
FirstNameTranscription = p.FirstNameTranscription,
|
|
LastNameTranscription = p.LastNameTranscription,
|
|
FullName = p.FirstName + " " + p.LastName,
|
|
FullNameTranscription = p.FirstNameTranscription + " " + p.LastNameTranscription,
|
|
BirthDate = p.BirthDate,
|
|
CountryName = p.CountryCodeNavigation.Name,
|
|
RegistrationsCount = p.Registrations.Count
|
|
})
|
|
.FirstAsync();
|
|
|
|
return PartialView("_PersonRow", rowModel);
|
|
}
|
|
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Delete(int id, SieveModel sieveModel)
|
|
{
|
|
var person = await ctx.People
|
|
.Include(p => p.Registrations)
|
|
.FirstOrDefaultAsync(p => p.Id == id);
|
|
|
|
if (person is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
if (person.Registrations.Count > 0)
|
|
{
|
|
Response.StatusCode = StatusCodes.Status409Conflict;
|
|
return Content("The person cannot be deleted because registrations exist.");
|
|
}
|
|
|
|
ctx.People.Remove(person);
|
|
var deletedName = $"{person.FirstName} {person.LastName}";
|
|
await ctx.SaveChangesAsync();
|
|
|
|
Response.Headers[Constants.HtmxHeaders.Trigger] = JsonSerializer.Serialize(new Dictionary<string, object?>
|
|
{
|
|
[Constants.HtmxEvents.ShowToast] = new
|
|
{
|
|
variant = Constants.ToastVariants.Success,
|
|
title = Constants.ToastTitles.Success,
|
|
message = $"Person '{deletedName}' was deleted successfully."
|
|
}
|
|
});
|
|
|
|
await PopulatePeopleCountryFilterViewDataAsync(sieveModel.Filters);
|
|
var viewModel = await BuildPeopleListAsync(sieveModel);
|
|
return PartialView("_PeopleList", viewModel);
|
|
}
|
|
|
|
private async Task<PagedList<PersonViewModel>> BuildPeopleListAsync(SieveModel sieveModel)
|
|
{
|
|
sieveModel.SetDefaultPagingAndSorting(pagingSettings.PageSize, "LastNameTranscription");
|
|
var normalizedFilters = sieveModel.Filters?.Trim() ?? string.Empty;
|
|
var nameFilter = SieveModelExtensions.ExtractFilterValue(normalizedFilters, "FullNameTranscription");
|
|
|
|
var baseQuery = ctx.People
|
|
.Select(p => new PersonViewModel
|
|
{
|
|
Id = p.Id,
|
|
FirstName = p.FirstName,
|
|
LastName = p.LastName,
|
|
FirstNameTranscription = p.FirstNameTranscription,
|
|
LastNameTranscription = p.LastNameTranscription,
|
|
FullName = p.FirstName + " " + p.LastName,
|
|
FullNameTranscription = p.FirstNameTranscription + " " + p.LastNameTranscription,
|
|
CountryCode = p.CountryCode,
|
|
BirthDate = p.BirthDate,
|
|
CountryName = p.CountryCodeNavigation.Name,
|
|
RegistrationsCount = p.Registrations.Count
|
|
});
|
|
|
|
var totalCount = await baseQuery.CountAsync();
|
|
var filteredCount = string.IsNullOrWhiteSpace(normalizedFilters)
|
|
? totalCount
|
|
: await sieveProcessor
|
|
.Apply(
|
|
sieveModel,
|
|
baseQuery,
|
|
applyFiltering: true,
|
|
applySorting: false,
|
|
applyPagination: false)
|
|
.CountAsync();
|
|
|
|
var pagingInfo = new PagingInfo
|
|
{
|
|
FilteredItemsCount = filteredCount,
|
|
TotalItemsCount = totalCount,
|
|
ItemsPerPage = sieveModel.PageSize!.Value,
|
|
CurrentPage = sieveModel.Page!.Value,
|
|
Sorts = sieveModel.Sorts ?? "LastNameTranscription",
|
|
Filters = normalizedFilters,
|
|
NameFilter = nameFilter
|
|
};
|
|
|
|
if (pagingInfo.CurrentPage > pagingInfo.TotalPages)
|
|
{
|
|
pagingInfo.CurrentPage = pagingInfo.TotalPages;
|
|
sieveModel.Page = pagingInfo.CurrentPage;
|
|
}
|
|
|
|
var people = await sieveProcessor
|
|
.Apply(sieveModel, baseQuery)
|
|
.ToListAsync();
|
|
|
|
return new PagedList<PersonViewModel>(people, pagingInfo);
|
|
}
|
|
|
|
private async Task<List<SelectListItem>> GetCountryOptionsAsync(string? selectedCode = null)
|
|
{
|
|
return await ctx.Countries
|
|
.OrderBy(c => c.Name)
|
|
.Select(c => new SelectListItem
|
|
{
|
|
Value = c.Code,
|
|
Text = c.Name,
|
|
Selected = c.Code == selectedCode
|
|
})
|
|
.ToListAsync();
|
|
}
|
|
|
|
private async Task PopulatePeopleCountryFilterViewDataAsync(string? filters)
|
|
{
|
|
var countryFilter = SieveModelExtensions.ExtractFilterValue(filters?.Trim() ?? string.Empty, "CountryCode", "==");
|
|
ViewData[Constants.ViewDataKeys.PeopleCountryOptions] = await GetCountryOptionsAsync(countryFilter);
|
|
ViewData[Constants.ViewDataKeys.PeopleCountryFilter] = countryFilter;
|
|
}
|
|
|
|
}
|