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) { this.ctx = ctx; this.sieveProcessor = sieveProcessor; this.pagingSettings = pagingSettings.Value; } public async Task 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 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 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 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 { [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 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 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 { [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> 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(people, pagingInfo); } private async Task> 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; } }