From 4df81c869cfce2d450a7e9c2d9ede4b51e753a1f Mon Sep 17 00:00:00 2001 From: joeprogrammer88 Date: Fri, 8 Aug 2025 16:14:15 -0700 Subject: [PATCH 01/19] Add image upload functionality for staff management - Updated AboutController to include ImageService for handling image uploads and resizing. - Introduced CreateStaffViewModel and EditStaffViewModel for managing staff data, including image URLs. - Added ImageUrl property to Staff entity and updated ApplicationDbContextModelSnapshot. - Initialized Staff, Board, and SteeringCommittee lists in AboutUsModel. - Registered ImageService in Program.cs for dependency injection. - Updated CreateStaff and EditStaff views to support file uploads and validation. - Enhanced About.cshtml layout to display staff with images using Bootstrap cards. - Added PeopleController for CRUD operations on staff with authorization checks. - Created migration files to add ImageUrl column to People table. - Added FormFileFromStream and ImageService classes for image processing. - Created StaffViewModels for managing staff creation and editing. - Updated/Create views (Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml, Index.cshtml) to support new functionality. --- PC2/Controllers/AboutController.cs | 146 ++++- PC2/Controllers/PeopleController.cs | 148 +++++ ...50807162448_AddImageUrlToStaff.Designer.cs | 537 ++++++++++++++++++ .../20250807162448_AddImageUrlToStaff.cs | 28 + .../ApplicationDbContextModelSnapshot.cs | 3 + PC2/Models/AboutUsModel.cs | 6 +- PC2/Models/FormFileFromStream.cs | 43 ++ PC2/Models/People.cs | 6 + PC2/Models/ViewModels/StaffViewModels.cs | 129 +++++ PC2/PC2.csproj | 1 + PC2/Program.cs | 3 + PC2/Services/ImageService.cs | 128 +++++ PC2/Views/About/CreateStaff.cshtml | 98 +++- PC2/Views/About/EditStaff.cshtml | 149 ++++- PC2/Views/Home/About.cshtml | 253 ++++++--- PC2/Views/People/Create.cshtml | 40 ++ PC2/Views/People/Delete.cshtml | 32 ++ PC2/Views/People/Details.cshtml | 30 + PC2/Views/People/Edit.cshtml | 41 ++ PC2/Views/People/Index.cshtml | 41 ++ PC2/Views/Shared/_Layout.cshtml | 1 + 21 files changed, 1726 insertions(+), 137 deletions(-) create mode 100644 PC2/Controllers/PeopleController.cs create mode 100644 PC2/Data/Migrations/20250807162448_AddImageUrlToStaff.Designer.cs create mode 100644 PC2/Data/Migrations/20250807162448_AddImageUrlToStaff.cs create mode 100644 PC2/Models/FormFileFromStream.cs create mode 100644 PC2/Models/ViewModels/StaffViewModels.cs create mode 100644 PC2/Services/ImageService.cs create mode 100644 PC2/Views/People/Create.cshtml create mode 100644 PC2/Views/People/Delete.cshtml create mode 100644 PC2/Views/People/Details.cshtml create mode 100644 PC2/Views/People/Edit.cshtml create mode 100644 PC2/Views/People/Index.cshtml diff --git a/PC2/Controllers/AboutController.cs b/PC2/Controllers/AboutController.cs index 12012575..a8248552 100644 --- a/PC2/Controllers/AboutController.cs +++ b/PC2/Controllers/AboutController.cs @@ -4,6 +4,8 @@ using Microsoft.EntityFrameworkCore; using PC2.Data; using PC2.Models; +using PC2.Models.ViewModels; +using PC2.Services; namespace PC2.Controllers { @@ -12,15 +14,16 @@ public class AboutController : Controller { private readonly ApplicationDbContext _context; private readonly AzureBlobUploader _azureBlobUploader; - private readonly IWebHostEnvironment _hostingEnvironment; + private readonly ImageService _imageService; // Iwebhost environment is used to get the path to the wwwroot folder - public AboutController(ApplicationDbContext context, IWebHostEnvironment hostingEnvironment, AzureBlobUploader azureBlobUploader) + public AboutController(ApplicationDbContext context, IWebHostEnvironment hostingEnvironment, AzureBlobUploader azureBlobUploader, ImageService imageService) { _context = context; _hostingEnvironment = hostingEnvironment; _azureBlobUploader = azureBlobUploader; + _imageService = imageService; } public async Task IndexStaff() @@ -35,18 +38,31 @@ public async Task IndexStaff() [HttpGet] public IActionResult CreateStaff() { - return View(); + return View(new CreateStaffViewModel()); } [HttpPost] - public async Task CreateStaff(Staff staff) + public async Task CreateStaff(CreateStaffViewModel model) { if (ModelState.IsValid) { + var staff = new Staff + { + Name = model.Name, + Title = model.Title, + Phone = model.Phone, + Extension = model.Extension, + Email = model.Email, + PriorityOrder = model.PriorityOrder + }; + + // Handle image upload + await HandleImageUpload(model.PhotoFile, model.ImageUrl, staff); + await StaffDB.AddStaff(_context, staff); return RedirectToAction("IndexStaff"); } - return View(staff); + return View(model); } /// @@ -57,19 +73,133 @@ public async Task CreateStaff(Staff staff) [HttpGet] public async Task EditStaff(int id) { - return View(await StaffDB.GetStaffMember(_context, id)); + var staff = await StaffDB.GetStaffMember(_context, id); + if (staff == null) + { + return NotFound(); + } + + var model = new EditStaffViewModel + { + ID = staff.ID, + Name = staff.Name, + Title = staff.Title, + Phone = staff.Phone, + Extension = staff.Extension, + Email = staff.Email, + CurrentImageUrl = staff.ImageUrl, + ImageUrl = staff.ImageUrl, + PriorityOrder = staff.PriorityOrder + }; + + return View(model); } [HttpPost] - public async Task EditStaff(Staff staff) + public async Task EditStaff(EditStaffViewModel model) { if (ModelState.IsValid) { + var staff = await StaffDB.GetStaffMember(_context, model.ID); + if (staff == null) + { + return NotFound(); + } + + // Update basic properties + staff.Name = model.Name; + staff.Title = model.Title; + staff.Phone = model.Phone; + staff.Extension = model.Extension; + staff.Email = model.Email; + staff.PriorityOrder = model.PriorityOrder; + + // Handle photo removal + if (model.RemovePhoto) + { + await RemoveStaffPhoto(staff); + } + // Handle new photo upload + else if (model.PhotoFile != null || !string.IsNullOrEmpty(model.ImageUrl)) + { + await HandleImageUpload(model.PhotoFile, model.ImageUrl, staff, model.ID); + } + await StaffDB.SaveChanges(_context, staff); return RedirectToAction("IndexStaff"); } - return View(staff); + // If model is invalid, reload current image URL for display + model.CurrentImageUrl = (await StaffDB.GetStaffMember(_context, model.ID))?.ImageUrl; + return View(model); + } + + private async Task HandleImageUpload(IFormFile? photoFile, string? imageUrl, Staff staff, int? staffId = null) + { + try + { + // Priority: uploaded file over URL + if (photoFile != null && photoFile.Length > 0) + { + // Validate image file + if (!ImageService.IsValidImageFile(photoFile)) + { + throw new InvalidOperationException("Please upload a valid image file (JPEG, PNG, GIF, or BMP)."); + } + + // Delete old photo if updating existing staff + if (staffId.HasValue && !string.IsNullOrEmpty(staff.ImageUrl)) + { + await RemoveStaffPhoto(staff); + } + + // Generate safe filename + var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, staffId ?? 0); + + // Resize image + using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream()); + + // Create IFormFile from resized image + var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); + + // Upload to Azure Blob + staff.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); + } + else if (!string.IsNullOrEmpty(imageUrl) && Uri.IsWellFormedUriString(imageUrl, UriKind.Absolute)) + { + // Use provided URL + staff.ImageUrl = imageUrl; + } + } + catch (Exception ex) + { + // Log error and continue without setting image + // In a production app, you might want to add this error to ModelState + Console.WriteLine($"Error handling image upload: {ex.Message}"); + } + } + + private async Task RemoveStaffPhoto(Staff staff) + { + if (!string.IsNullOrEmpty(staff.ImageUrl)) + { + try + { + // Extract filename from URL for Azure Blob deletion + var fileName = staff.ImageUrl.Split('/').LastOrDefault(); + if (!string.IsNullOrEmpty(fileName)) + { + await _azureBlobUploader.DeleteFileAsync(fileName); + } + } + catch (Exception ex) + { + // Log error but continue + Console.WriteLine($"Error deleting photo: {ex.Message}"); + } + + staff.ImageUrl = null; + } } /// diff --git a/PC2/Controllers/PeopleController.cs b/PC2/Controllers/PeopleController.cs new file mode 100644 index 00000000..5936ecff --- /dev/null +++ b/PC2/Controllers/PeopleController.cs @@ -0,0 +1,148 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using PC2.Data; +using PC2.Models; + +namespace PC2.Controllers +{ + [Authorize(Roles = IdentityHelper.Admin)] + public class PeopleController : Controller + { + private readonly ApplicationDbContext _context; + + public PeopleController(ApplicationDbContext context) + { + _context = context; + } + + // GET: People + public async Task Index() + { + var people = await _context.People.OrderBy(p => p.PriorityOrder).ThenBy(p => p.Name).ToListAsync(); + return View(people); + } + + // GET: People/Details/5 + public async Task Details(int? id) + { + if (id == null) + { + return NotFound(); + } + + var person = await _context.People.FirstOrDefaultAsync(m => m.ID == id); + if (person == null) + { + return NotFound(); + } + + return View(person); + } + + // GET: People/Create + public IActionResult Create() + { + return View(); + } + + // POST: People/Create + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Create([Bind("Name,Title,PriorityOrder")] People person) + { + if (ModelState.IsValid) + { + _context.Add(person); + await _context.SaveChangesAsync(); + return RedirectToAction(nameof(Index)); + } + return View(person); + } + + // GET: People/Edit/5 + public async Task Edit(int? id) + { + if (id == null) + { + return NotFound(); + } + + var person = await _context.People.FindAsync(id); + if (person == null) + { + return NotFound(); + } + return View(person); + } + + // POST: People/Edit/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Edit(int id, [Bind("ID,Name,Title,PriorityOrder")] People person) + { + if (id != person.ID) + { + return NotFound(); + } + + if (ModelState.IsValid) + { + try + { + _context.Update(person); + await _context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!PersonExists(person.ID)) + { + return NotFound(); + } + else + { + throw; + } + } + return RedirectToAction(nameof(Index)); + } + return View(person); + } + + // GET: People/Delete/5 + public async Task Delete(int? id) + { + if (id == null) + { + return NotFound(); + } + + var person = await _context.People.FirstOrDefaultAsync(m => m.ID == id); + if (person == null) + { + return NotFound(); + } + + return View(person); + } + + // POST: People/Delete/5 + [HttpPost, ActionName("Delete")] + [ValidateAntiForgeryToken] + public async Task DeleteConfirmed(int id) + { + var person = await _context.People.FindAsync(id); + if (person != null) + { + _context.People.Remove(person); + await _context.SaveChangesAsync(); + } + return RedirectToAction(nameof(Index)); + } + + private bool PersonExists(int id) + { + return _context.People.Any(e => e.ID == id); + } + } +} \ No newline at end of file diff --git a/PC2/Data/Migrations/20250807162448_AddImageUrlToStaff.Designer.cs b/PC2/Data/Migrations/20250807162448_AddImageUrlToStaff.Designer.cs new file mode 100644 index 00000000..c41b5fd0 --- /dev/null +++ b/PC2/Data/Migrations/20250807162448_AddImageUrlToStaff.Designer.cs @@ -0,0 +1,537 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PC2.Data; + +#nullable disable + +namespace PC2.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250807162448_AddImageUrlToStaff")] + partial class AddImageUrlToStaff + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("AgencyAgencyCategory", b => + { + b.Property("AgenciesAgencyId") + .HasColumnType("int"); + + b.Property("AgencyCategoriesAgencyCategoryId") + .HasColumnType("int"); + + b.HasKey("AgenciesAgencyId", "AgencyCategoriesAgencyCategoryId"); + + b.HasIndex("AgencyCategoriesAgencyCategoryId"); + + b.ToTable("AgencyAgencyCategory"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("PC2.Models.Agency", b => + { + b.Property("AgencyId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("AgencyId")); + + b.Property("Address1") + .HasColumnType("nvarchar(max)"); + + b.Property("Address2") + .HasColumnType("nvarchar(max)"); + + b.Property("AgencyName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("AgencyName2") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .HasColumnType("nvarchar(max)"); + + b.Property("Contact") + .HasColumnType("nvarchar(max)"); + + b.Property("CrisisHelpHotline") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("Fax") + .HasColumnType("nvarchar(max)"); + + b.Property("MailingAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("TDD") + .HasColumnType("nvarchar(max)"); + + b.Property("TTY") + .HasColumnType("nvarchar(max)"); + + b.Property("TollFree") + .HasColumnType("nvarchar(max)"); + + b.Property("Website") + .HasColumnType("nvarchar(max)"); + + b.Property("Zip") + .HasColumnType("nvarchar(max)"); + + b.HasKey("AgencyId"); + + b.ToTable("Agency"); + }); + + modelBuilder.Entity("PC2.Models.AgencyCategory", b => + { + b.Property("AgencyCategoryId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("AgencyCategoryId")); + + b.Property("AgencyCategoryName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("AgencyCategoryId"); + + b.ToTable("AgencyCategory"); + }); + + modelBuilder.Entity("PC2.Models.CalendarEvent", b => + { + b.Property("CalendarEventID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("CalendarEventID")); + + b.Property("CountyEvent") + .HasColumnType("bit"); + + b.Property("DateOfEvent") + .HasColumnType("date"); + + b.Property("EndingTime") + .HasColumnType("time"); + + b.Property("EventDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PC2Event") + .HasColumnType("bit"); + + b.Property("StartingTime") + .HasColumnType("time"); + + b.HasKey("CalendarEventID"); + + b.ToTable("CalendarEvents"); + }); + + modelBuilder.Entity("PC2.Models.HousingProgram", b => + { + b.Property("HouseHoldSize") + .HasColumnType("int"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("MaximumIncome") + .HasColumnType("float"); + + b.HasKey("HouseHoldSize"); + + b.ToTable("HousingProgram"); + }); + + modelBuilder.Entity("PC2.Models.NewsletterFile", b => + { + b.Property("NewsletterId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("NewsletterId")); + + b.Property("Location") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("NewsletterId"); + + b.ToTable("NewsletterFile"); + }); + + modelBuilder.Entity("PC2.Models.People", b => + { + b.Property("ID") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("nvarchar(21)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PriorityOrder") + .HasColumnType("tinyint"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("ID"); + + b.ToTable("People"); + + b.HasDiscriminator().HasValue("People"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("PC2.Models.Board", b => + { + b.HasBaseType("PC2.Models.People"); + + b.Property("MembershipStart") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasDiscriminator().HasValue("Board"); + }); + + modelBuilder.Entity("PC2.Models.Staff", b => + { + b.HasBaseType("PC2.Models.People"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Extension") + .HasColumnType("int"); + + b.Property("ImageUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("Phone") + .HasColumnType("nvarchar(max)"); + + b.HasDiscriminator().HasValue("Staff"); + }); + + modelBuilder.Entity("PC2.Models.SteeringCommittee", b => + { + b.HasBaseType("PC2.Models.People"); + + b.HasDiscriminator().HasValue("SteeringCommittee"); + }); + + modelBuilder.Entity("AgencyAgencyCategory", b => + { + b.HasOne("PC2.Models.Agency", null) + .WithMany() + .HasForeignKey("AgenciesAgencyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PC2.Models.AgencyCategory", null) + .WithMany() + .HasForeignKey("AgencyCategoriesAgencyCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/PC2/Data/Migrations/20250807162448_AddImageUrlToStaff.cs b/PC2/Data/Migrations/20250807162448_AddImageUrlToStaff.cs new file mode 100644 index 00000000..31fbf7fa --- /dev/null +++ b/PC2/Data/Migrations/20250807162448_AddImageUrlToStaff.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PC2.Data.Migrations +{ + /// + public partial class AddImageUrlToStaff : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ImageUrl", + table: "People", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ImageUrl", + table: "People"); + } + } +} diff --git a/PC2/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/PC2/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 24ed0f5e..9415dce2 100644 --- a/PC2/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/PC2/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -447,6 +447,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Extension") .HasColumnType("int"); + b.Property("ImageUrl") + .HasColumnType("nvarchar(max)"); + b.Property("Phone") .HasColumnType("nvarchar(max)"); diff --git a/PC2/Models/AboutUsModel.cs b/PC2/Models/AboutUsModel.cs index ea53ab84..f3f3af1e 100644 --- a/PC2/Models/AboutUsModel.cs +++ b/PC2/Models/AboutUsModel.cs @@ -2,10 +2,10 @@ { public class AboutUsModel { - public List Staff { get; set; } + public List Staff { get; set; } = []; - public List Board { get; set; } + public List Board { get; set; } = []; - public List SteeringCommittee { get; set; } + public List SteeringCommittee { get; set; } = []; } } diff --git a/PC2/Models/FormFileFromStream.cs b/PC2/Models/FormFileFromStream.cs new file mode 100644 index 00000000..cff336d3 --- /dev/null +++ b/PC2/Models/FormFileFromStream.cs @@ -0,0 +1,43 @@ +namespace PC2.Models +{ + /// + /// Implementation of IFormFile that wraps a Stream + /// Used for creating IFormFile from resized image streams + /// + public class FormFileFromStream : IFormFile + { + private readonly Stream _stream; + private readonly string _name; + private readonly string _contentType; + + public FormFileFromStream(Stream stream, string name, string contentType) + { + _stream = stream; + _name = name; + _contentType = contentType; + } + + public string ContentType => _contentType; + public string ContentDisposition => $"form-data; name=\"{Name}\"; filename=\"{FileName}\""; + public IHeaderDictionary Headers => new HeaderDictionary(); + public long Length => _stream.Length; + public string Name => _name; + public string FileName => _name; + + public void CopyTo(Stream target) + { + _stream.CopyTo(target); + } + + public Task CopyToAsync(Stream target, CancellationToken cancellationToken = default) + { + return _stream.CopyToAsync(target, cancellationToken); + } + + public Stream OpenReadStream() + { + _stream.Position = 0; + return _stream; + } + } +} \ No newline at end of file diff --git a/PC2/Models/People.cs b/PC2/Models/People.cs index 674e88bd..4f4e507c 100644 --- a/PC2/Models/People.cs +++ b/PC2/Models/People.cs @@ -50,6 +50,12 @@ public class Staff : People [EmailAddress] public required string Email { get; set; } + /// + /// URL to the staff member's photo/image. + /// + [DataType(DataType.Url)] + public string? ImageUrl { get; set; } + /// /// Gets a formatted display of the phone number and extension. /// diff --git a/PC2/Models/ViewModels/StaffViewModels.cs b/PC2/Models/ViewModels/StaffViewModels.cs new file mode 100644 index 00000000..8aa03d62 --- /dev/null +++ b/PC2/Models/ViewModels/StaffViewModels.cs @@ -0,0 +1,129 @@ +using System.ComponentModel.DataAnnotations; + +namespace PC2.Models.ViewModels +{ + /// + /// ViewModel for creating a new staff member with file upload support + /// + public class CreateStaffViewModel + { + /// + /// The person's full name + /// + [Required] + public string Name { get; set; } = string.Empty; + + /// + /// Position title + /// + public string? Title { get; set; } + + /// + /// The staff/person's phone number. + /// + public string? Phone { get; set; } + + /// + /// The phone extension. + /// + public int? Extension { get; set; } + + /// + /// The staff/person's email address. + /// + [Required] + [DataType(DataType.EmailAddress)] + [EmailAddress] + public string Email { get; set; } = string.Empty; + + /// + /// Photo file upload + /// + [Display(Name = "Photo")] + public IFormFile? PhotoFile { get; set; } + + /// + /// Alternative: Photo URL (if not uploading a file) + /// + [DataType(DataType.Url)] + [Display(Name = "Photo URL (if not uploading file)")] + public string? ImageUrl { get; set; } + + /// + /// Used to sort People by their display priority. Lower number + /// is a higher priority + /// + [Required] + [Display(Name = "Sort Priority")] + public byte PriorityOrder { get; set; } = 10; + } + + /// + /// ViewModel for editing an existing staff member with file upload support + /// + public class EditStaffViewModel + { + public int ID { get; set; } + + /// + /// The person's full name + /// + [Required] + public string Name { get; set; } = string.Empty; + + /// + /// Position title + /// + public string? Title { get; set; } + + /// + /// The staff/person's phone number. + /// + public string? Phone { get; set; } + + /// + /// The phone extension. + /// + public int? Extension { get; set; } + + /// + /// The staff/person's email address. + /// + [Required] + [DataType(DataType.EmailAddress)] + [EmailAddress] + public string Email { get; set; } = string.Empty; + + /// + /// Current photo URL + /// + public string? CurrentImageUrl { get; set; } + + /// + /// New photo file upload + /// + [Display(Name = "New Photo")] + public IFormFile? PhotoFile { get; set; } + + /// + /// Alternative: Photo URL (if not uploading a file) + /// + [DataType(DataType.Url)] + [Display(Name = "Photo URL (if not uploading file)")] + public string? ImageUrl { get; set; } + + /// + /// Whether to remove the current photo + /// + [Display(Name = "Remove current photo")] + public bool RemovePhoto { get; set; } + + /// + /// Used to sort People by their display priority. Lower number + /// is a higher priority + /// + [Required] + [Display(Name = "Sort Priority")] + public byte PriorityOrder { get; set; } = 10; + } +} \ No newline at end of file diff --git a/PC2/PC2.csproj b/PC2/PC2.csproj index d92293b9..17a51231 100644 --- a/PC2/PC2.csproj +++ b/PC2/PC2.csproj @@ -28,6 +28,7 @@ + diff --git a/PC2/Program.cs b/PC2/Program.cs index 970098cc..9ae1f768 100644 --- a/PC2/Program.cs +++ b/PC2/Program.cs @@ -18,6 +18,9 @@ // Register AzureBlobUploader for DI builder.Services.AddSingleton(); +// Register ImageService for DI +builder.Services.AddScoped(); + builder.Services.AddApplicationInsightsTelemetry(options => options.ConnectionString = builder.Configuration.GetSection("APPLICATIONINSIGHTS_CONNECTION_STRING").Value); diff --git a/PC2/Services/ImageService.cs b/PC2/Services/ImageService.cs new file mode 100644 index 00000000..4a7b91f6 --- /dev/null +++ b/PC2/Services/ImageService.cs @@ -0,0 +1,128 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Bmp; + +namespace PC2.Services +{ + /// + /// Service for image processing operations like resizing using ImageSharp + /// + public class ImageService + { + private readonly ILogger _logger; + + public ImageService(ILogger logger) + { + _logger = logger; + } + + /// + /// Resizes an image to the specified maximum dimensions while maintaining aspect ratio + /// + /// The input image stream + /// Maximum width + /// Maximum height + /// Resized image as a memory stream + public async Task ResizeImageAsync(Stream imageStream, int maxWidth = 800, int maxHeight = 600) + { + try + { + using var image = await Image.LoadAsync(imageStream); + + // Calculate new dimensions while maintaining aspect ratio + (int newWidth, int newHeight) = CalculateResizeDimensions(image.Width, image.Height, maxWidth, maxHeight); + + // If image is already smaller than max dimensions, return original + if (newWidth == image.Width && newHeight == image.Height) + { + var originalStream = new MemoryStream(); + imageStream.Position = 0; + await imageStream.CopyToAsync(originalStream); + originalStream.Position = 0; + return originalStream; + } + + // Resize the image + image.Mutate(x => x.Resize(new ResizeOptions + { + Size = new Size(newWidth, newHeight), + Mode = ResizeMode.Max, // Ensure it fits within max dimensions + Sampler = KnownResamplers.Lanczos3 // High quality resampling (more CPU intensive) + })); + + // Save to memory stream + var outputStream = new MemoryStream(); + + // Use JPEG format with good quality for most cases + await image.SaveAsJpegAsync(outputStream, new JpegEncoder + { + Quality = 85 + }); + + outputStream.Position = 0; + return outputStream; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error resizing image"); + throw new InvalidOperationException("Failed to resize image", ex); + } + } + + /// + /// Calculates new dimensions for resizing while maintaining aspect ratio. Minimizes the size to fit within maxWidth and maxHeight. + /// + private static (int width, int height) CalculateResizeDimensions(int originalWidth, int originalHeight, int maxWidth, int maxHeight) + { + // If image is already within bounds, return original size + if (originalWidth <= maxWidth && originalHeight <= maxHeight) + return (originalWidth, originalHeight); + + // Calculate scaling ratios + double widthRatio = (double)maxWidth / originalWidth; + double heightRatio = (double)maxHeight / originalHeight; + + // Use the smaller ratio to ensure image fits within both dimensions + double ratio = Math.Min(widthRatio, heightRatio); + + int newWidth = (int)(originalWidth * ratio); + int newHeight = (int)(originalHeight * ratio); + + return (newWidth, newHeight); + } + + /// + /// Validates if the uploaded file is a valid image + /// + public static bool IsValidImageFile(IFormFile file) + { + if (file == null || file.Length == 0) + return false; + + var allowedMimeTypes = new[] + { + "image/jpeg", + "image/jpg", + "image/png", + "image/gif", + "image/bmp", + "image/webp" + }; + + return allowedMimeTypes.Contains(file.ContentType?.ToLower()); + } + + /// + /// Gets a safe filename for uploaded images + /// + public static string GetSafeImageFileName(string originalFileName, int staffId) + { + var extension = Path.GetExtension(originalFileName); + var timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); + return $"staff_{staffId}_{timestamp}{extension}"; + } + } +} \ No newline at end of file diff --git a/PC2/Views/About/CreateStaff.cshtml b/PC2/Views/About/CreateStaff.cshtml index 98ffb295..e4f40e16 100644 --- a/PC2/Views/About/CreateStaff.cshtml +++ b/PC2/Views/About/CreateStaff.cshtml @@ -1,4 +1,4 @@ -@model PC2.Models.Staff +@model PC2.Models.ViewModels.CreateStaffViewModel @{ ViewData["Title"] = "Create Staff Member"; @@ -7,50 +7,106 @@

@ViewData["Title"]


-
-
+
+
-
+ +
-
+ +
-
- - - -
-
- - - + +
+
+
+ + + +
+
+
+
+ + + +
+
-
+ +
- +
-
- + +
+
+
Photo
+
+
+
+ + + + + Upload a photo file (JPEG, PNG, GIF, or BMP). Large images will be automatically resized. + +
+ +
+ OR +
+ +
+ + + + + Enter a URL to an existing photo online + +
+
+
+ +
+
+
+ Cancel
- +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} + +} diff --git a/PC2/Views/About/EditStaff.cshtml b/PC2/Views/About/EditStaff.cshtml index 4bbfc581..01502bc3 100644 --- a/PC2/Views/About/EditStaff.cshtml +++ b/PC2/Views/About/EditStaff.cshtml @@ -1,4 +1,4 @@ -@model PC2.Models.Staff +@model PC2.Models.ViewModels.EditStaffViewModel @{ ViewData["Title"] = "Edit Staff Member"; @@ -8,51 +8,158 @@
-
-
+
+
-
+ +
-
+ +
-
- - - -
-
- - - + +
+
+
+ + + +
+
+
+
+ + + +
+
-
+ +
- +
-
- + +
+
+
Photo
+
+
+ @if (!string.IsNullOrEmpty(Model.CurrentImageUrl)) + { +
+ +
+ @Model.Name +
+
+ +
+ + +
+ +
+
+ Update Photo: +
+
+ } + +
+ + + + + Upload a new photo file (JPEG, PNG, GIF, or BMP). Large images will be automatically resized. + +
+ +
+ OR +
+ +
+ + + + + Enter a URL to an existing photo online + +
+
+
+ +
+
+
+ Cancel
- +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} + +} diff --git a/PC2/Views/Home/About.cshtml b/PC2/Views/Home/About.cshtml index 4fca9b50..47783007 100644 --- a/PC2/Views/Home/About.cshtml +++ b/PC2/Views/Home/About.cshtml @@ -1,41 +1,48 @@ -@model PC2.Models.AboutUsModel +@model AboutUsModel @{ ViewData["Title"] = "About Us"; } +@section Head{ + + /* Minimal custom CSS for circular images */ + .staff-avatar { + width: 150px; + height: 150px; + object-fit: cover; + } + +}
@@ -54,69 +61,147 @@
-

@ViewData["Title"]

+

@ViewData["Title"]

Our Staff

-
- - - @for (int i = 0; i < Model.Staff.Count; i++) - { - - - - } - -
-

@Model.Staff[i].Name

-

@Model.Staff[i].Title

- @if(Model.Staff[i].PhoneDisplay != null) + @* Display staff with images using Bootstrap Cards *@ + @if (Model.Staff.Any(s => !string.IsNullOrEmpty(s.ImageUrl))) + { +
+ @foreach (var staff in Model.Staff.Where(s => !string.IsNullOrEmpty(s.ImageUrl))) + { +
+
+
+ @staff.Name +
@staff.Name
+ @if (!string.IsNullOrEmpty(staff.Title)) { -

- @Model.Staff[i].PhoneDisplay -

+

@staff.Title

} - @Model.Staff[i].Email -
-
+
+ @if(staff.PhoneDisplay != null) + { +

+ @staff.PhoneDisplay +

+ } +

+ @staff.Email +

+
+
+
+
+ } +
+ } + + @* Fall back to table for staff without images *@ + @if (Model.Staff.Any(s => string.IsNullOrEmpty(s.ImageUrl))) + { +
+
+
+
+
Additional Staff Members
+
+
+
+ + + @foreach (var staff in Model.Staff.Where(s => string.IsNullOrEmpty(s.ImageUrl))) + { + + + + } + +
+

@staff.Name

+ @if (!string.IsNullOrEmpty(staff.Title)) + { +

@staff.Title

+ } + @if(staff.PhoneDisplay != null) + { +

+ @staff.PhoneDisplay +

+ } +

+ @staff.Email +

+
+
+
+
+
+
+ } +

Our Board

-
- - - @for (int i = 0; i < Model.Board.Count; i++) - { - - - - } - -
-

@Model.Board[i].Name

- @if (Model.Board[i].Title != null) - { -

@Model.Board[i].Title

- } -

Member since @Model.Board[i].MembershipStart

-
+
+
+
+
+
Board Members
+
+
+
+ + + @for (int i = 0; i < Model.Board.Count; i++) + { + + + + } + +
+

@Model.Board[i].Name

+ @if (Model.Board[i].Title != null) + { +

@Model.Board[i].Title

+ } +

Member since @Model.Board[i].MembershipStart

+
+
+
+
+
+ @if (Model.SteeringCommittee.Count > 0) {

Our Steering Committee

-
- - - @for (int i = 0; i < Model.SteeringCommittee.Count; i++) - { - - - - } - -
-

@Model.SteeringCommittee[i].Name

-

@Model.SteeringCommittee[i].Title

-
+
+
+
+
+
Steering Committee Members
+
+
+
+ + + @for (int i = 0; i < Model.SteeringCommittee.Count; i++) + { + + + + } + +
+

@Model.SteeringCommittee[i].Name

+

@Model.SteeringCommittee[i].Title

+
+
+
+
+
- } \ No newline at end of file + } +
\ No newline at end of file diff --git a/PC2/Views/People/Create.cshtml b/PC2/Views/People/Create.cshtml new file mode 100644 index 00000000..7d7e55c7 --- /dev/null +++ b/PC2/Views/People/Create.cshtml @@ -0,0 +1,40 @@ +@model PC2.Models.People + +@{ + ViewData["Title"] = "Create Person"; +} + +

@ViewData["Title"]

+ +
+
+
+
+
+
+ + + +
+
+ + + +
+
+ + + + Lower numbers have higher priority (0 = highest) +
+
+ + Cancel +
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} \ No newline at end of file diff --git a/PC2/Views/People/Delete.cshtml b/PC2/Views/People/Delete.cshtml new file mode 100644 index 00000000..7f6c4eb6 --- /dev/null +++ b/PC2/Views/People/Delete.cshtml @@ -0,0 +1,32 @@ +@model PC2.Models.People + +@{ + ViewData["Title"] = "Delete Person"; +} + +

@ViewData["Title"]

+ +

Are you sure you want to delete this person?

+
+

@Model.Name

+
+
+
@Html.DisplayNameFor(model => model.Name)
+
@Html.DisplayFor(model => model.Name)
+ +
@Html.DisplayNameFor(model => model.Title)
+
@Html.DisplayFor(model => model.Title)
+ +
@Html.DisplayNameFor(model => model.PriorityOrder)
+
@Html.DisplayFor(model => model.PriorityOrder)
+ +
Type
+
@Model.GetType().Name
+
+ +
+ + + Cancel +
+
\ No newline at end of file diff --git a/PC2/Views/People/Details.cshtml b/PC2/Views/People/Details.cshtml new file mode 100644 index 00000000..426c1a64 --- /dev/null +++ b/PC2/Views/People/Details.cshtml @@ -0,0 +1,30 @@ +@model PC2.Models.People + +@{ + ViewData["Title"] = "Person Details"; +} + +

@ViewData["Title"]

+ +
+

@Model.Name

+
+
+
@Html.DisplayNameFor(model => model.Name)
+
@Html.DisplayFor(model => model.Name)
+ +
@Html.DisplayNameFor(model => model.Title)
+
@Html.DisplayFor(model => model.Title)
+ +
@Html.DisplayNameFor(model => model.PriorityOrder)
+
@Html.DisplayFor(model => model.PriorityOrder)
+ +
Type
+
@Model.GetType().Name
+
+
+ + \ No newline at end of file diff --git a/PC2/Views/People/Edit.cshtml b/PC2/Views/People/Edit.cshtml new file mode 100644 index 00000000..17f78d62 --- /dev/null +++ b/PC2/Views/People/Edit.cshtml @@ -0,0 +1,41 @@ +@model PC2.Models.People + +@{ + ViewData["Title"] = "Edit Person"; +} + +

Edit @Model.Name

+ +
+
+
+
+
+ +
+ + + +
+
+ + + +
+
+ + + + Lower numbers have higher priority (0 = highest) +
+
+ + Cancel +
+
+
+
+ +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} \ No newline at end of file diff --git a/PC2/Views/People/Index.cshtml b/PC2/Views/People/Index.cshtml new file mode 100644 index 00000000..5fe250f2 --- /dev/null +++ b/PC2/Views/People/Index.cshtml @@ -0,0 +1,41 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "People Management"; +} + +

@ViewData["Title"]

+ +

+ Create New Person +

+ +
+ + + + + + + + + + + + @foreach (var item in Model) + { + + + + + + + + } + +
@Html.DisplayNameFor(model => model.Name)@Html.DisplayNameFor(model => model.Title)@Html.DisplayNameFor(model => model.PriorityOrder)TypeActions
@Html.DisplayFor(modelItem => item.Name)@Html.DisplayFor(modelItem => item.Title)@Html.DisplayFor(modelItem => item.PriorityOrder)@item.GetType().Name + Edit + Details + Delete +
+
\ No newline at end of file diff --git a/PC2/Views/Shared/_Layout.cshtml b/PC2/Views/Shared/_Layout.cshtml index e4897e38..bc17a5c8 100644 --- a/PC2/Views/Shared/_Layout.cshtml +++ b/PC2/Views/Shared/_Layout.cshtml @@ -107,6 +107,7 @@ cfg: { // Application Insights Configuration
  • Create Board Member
  • View Steering Committee
  • Create Steering Committee Member
  • +
  • Manage All People
  • Upload Newsletter
  • From 4781fd477a75755f7b479cb11f4729e26759cb3d Mon Sep 17 00:00:00 2001 From: joeprogrammer88 Date: Wed, 5 Nov 2025 13:49:15 -0800 Subject: [PATCH 02/19] Fixed missing field left out from merging --- PC2/Controllers/AboutController.cs | 289 +++++++++++++++-------------- 1 file changed, 145 insertions(+), 144 deletions(-) diff --git a/PC2/Controllers/AboutController.cs b/PC2/Controllers/AboutController.cs index 9c7a196f..eda9a9b2 100644 --- a/PC2/Controllers/AboutController.cs +++ b/PC2/Controllers/AboutController.cs @@ -14,190 +14,191 @@ public class AboutController : Controller { private readonly ApplicationDbContext _context; private readonly AzureBlobUploader _azureBlobUploader; + private readonly ImageService _imageService; - // Iwebhost environment is used to get the path to the wwwroot folder - public AboutController(ApplicationDbContext context, AzureBlobUploader azureBlobUploader, ImageService imageService) - { - _context = context; - _azureBlobUploader = azureBlobUploader; - _imageService = imageService; - } + // Iwebhost environment is used to get the path to the wwwroot folder + public AboutController(ApplicationDbContext context, AzureBlobUploader azureBlobUploader, ImageService imageService) + { + _context = context; + _azureBlobUploader = azureBlobUploader; + _imageService = imageService; + } public async Task IndexStaff() { return View(await StaffDB.GetAllStaffForEditing(_context)); } - /// - /// Creates a staff member - /// - /// - [HttpGet] - public IActionResult CreateStaff() - { - return View(new CreateStaffViewModel()); - } + /// + /// Creates a staff member + /// + /// + [HttpGet] + public IActionResult CreateStaff() + { + return View(new CreateStaffViewModel()); + } - [HttpPost] - public async Task CreateStaff(CreateStaffViewModel model) + [HttpPost] + public async Task CreateStaff(CreateStaffViewModel model) + { + if (ModelState.IsValid) { - if (ModelState.IsValid) + var staff = new Staff { - var staff = new Staff - { - Name = model.Name, - Title = model.Title, - Phone = model.Phone, - Extension = model.Extension, - Email = model.Email, - PriorityOrder = model.PriorityOrder - }; + Name = model.Name, + Title = model.Title, + Phone = model.Phone, + Extension = model.Extension, + Email = model.Email, + PriorityOrder = model.PriorityOrder + }; - // Handle image upload - await HandleImageUpload(model.PhotoFile, model.ImageUrl, staff); + // Handle image upload + await HandleImageUpload(model.PhotoFile, model.ImageUrl, staff); - await StaffDB.AddStaff(_context, staff); - return RedirectToAction("IndexStaff"); - } - return View(model); + await StaffDB.AddStaff(_context, staff); + return RedirectToAction("IndexStaff"); + } + return View(model); + } + + /// + /// Edits a staff member + /// + /// The id for the staff member + /// + [HttpGet] + public async Task EditStaff(int id) + { + var staff = await StaffDB.GetStaffMember(_context, id); + if (staff == null) + { + return NotFound(); } - /// - /// Edits a staff member - /// - /// The id for the staff member - /// - [HttpGet] - public async Task EditStaff(int id) + var model = new EditStaffViewModel + { + ID = staff.ID, + Name = staff.Name, + Title = staff.Title, + Phone = staff.Phone, + Extension = staff.Extension, + Email = staff.Email, + CurrentImageUrl = staff.ImageUrl, + ImageUrl = staff.ImageUrl, + PriorityOrder = staff.PriorityOrder + }; + + return View(model); + } + + [HttpPost] + public async Task EditStaff(EditStaffViewModel model) + { + if (ModelState.IsValid) { - var staff = await StaffDB.GetStaffMember(_context, id); + var staff = await StaffDB.GetStaffMember(_context, model.ID); if (staff == null) { return NotFound(); } - var model = new EditStaffViewModel + // Update basic properties + staff.Name = model.Name; + staff.Title = model.Title; + staff.Phone = model.Phone; + staff.Extension = model.Extension; + staff.Email = model.Email; + staff.PriorityOrder = model.PriorityOrder; + + // Handle photo removal + if (model.RemovePhoto) { - ID = staff.ID, - Name = staff.Name, - Title = staff.Title, - Phone = staff.Phone, - Extension = staff.Extension, - Email = staff.Email, - CurrentImageUrl = staff.ImageUrl, - ImageUrl = staff.ImageUrl, - PriorityOrder = staff.PriorityOrder - }; + await RemoveStaffPhoto(staff); + } + // Handle new photo upload + else if (model.PhotoFile != null || !string.IsNullOrEmpty(model.ImageUrl)) + { + await HandleImageUpload(model.PhotoFile, model.ImageUrl, staff, model.ID); + } - return View(model); + await StaffDB.SaveChanges(_context, staff); + return RedirectToAction("IndexStaff"); } - [HttpPost] - public async Task EditStaff(EditStaffViewModel model) + // If model is invalid, reload current image URL for display + model.CurrentImageUrl = (await StaffDB.GetStaffMember(_context, model.ID))?.ImageUrl; + return View(model); + } + + private async Task HandleImageUpload(IFormFile? photoFile, string? imageUrl, Staff staff, int? staffId = null) + { + try { - if (ModelState.IsValid) + // Priority: uploaded file over URL + if (photoFile != null && photoFile.Length > 0) { - var staff = await StaffDB.GetStaffMember(_context, model.ID); - if (staff == null) + // Validate image file + if (!ImageService.IsValidImageFile(photoFile)) { - return NotFound(); + throw new InvalidOperationException("Please upload a valid image file (JPEG, PNG, GIF, or BMP)."); } - // Update basic properties - staff.Name = model.Name; - staff.Title = model.Title; - staff.Phone = model.Phone; - staff.Extension = model.Extension; - staff.Email = model.Email; - staff.PriorityOrder = model.PriorityOrder; - - // Handle photo removal - if (model.RemovePhoto) + // Delete old photo if updating existing staff + if (staffId.HasValue && !string.IsNullOrEmpty(staff.ImageUrl)) { await RemoveStaffPhoto(staff); } - // Handle new photo upload - else if (model.PhotoFile != null || !string.IsNullOrEmpty(model.ImageUrl)) - { - await HandleImageUpload(model.PhotoFile, model.ImageUrl, staff, model.ID); - } - - await StaffDB.SaveChanges(_context, staff); - return RedirectToAction("IndexStaff"); - } - - // If model is invalid, reload current image URL for display - model.CurrentImageUrl = (await StaffDB.GetStaffMember(_context, model.ID))?.ImageUrl; - return View(model); - } - private async Task HandleImageUpload(IFormFile? photoFile, string? imageUrl, Staff staff, int? staffId = null) - { - try - { - // Priority: uploaded file over URL - if (photoFile != null && photoFile.Length > 0) - { - // Validate image file - if (!ImageService.IsValidImageFile(photoFile)) - { - throw new InvalidOperationException("Please upload a valid image file (JPEG, PNG, GIF, or BMP)."); - } - - // Delete old photo if updating existing staff - if (staffId.HasValue && !string.IsNullOrEmpty(staff.ImageUrl)) - { - await RemoveStaffPhoto(staff); - } + // Generate safe filename + var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, staffId ?? 0); - // Generate safe filename - var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, staffId ?? 0); + // Resize image + using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream()); - // Resize image - using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream()); + // Create IFormFile from resized image + var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); - // Create IFormFile from resized image - var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); - - // Upload to Azure Blob - staff.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); - } - else if (!string.IsNullOrEmpty(imageUrl) && Uri.IsWellFormedUriString(imageUrl, UriKind.Absolute)) - { - // Use provided URL - staff.ImageUrl = imageUrl; - } + // Upload to Azure Blob + staff.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); } - catch (Exception ex) + else if (!string.IsNullOrEmpty(imageUrl) && Uri.IsWellFormedUriString(imageUrl, UriKind.Absolute)) { - // Log error and continue without setting image - // In a production app, you might want to add this error to ModelState - Console.WriteLine($"Error handling image upload: {ex.Message}"); + // Use provided URL + staff.ImageUrl = imageUrl; } } + catch (Exception ex) + { + // Log error and continue without setting image + // In a production app, you might want to add this error to ModelState + Console.WriteLine($"Error handling image upload: {ex.Message}"); + } + } - private async Task RemoveStaffPhoto(Staff staff) + private async Task RemoveStaffPhoto(Staff staff) + { + if (!string.IsNullOrEmpty(staff.ImageUrl)) { - if (!string.IsNullOrEmpty(staff.ImageUrl)) + try { - try - { - // Extract filename from URL for Azure Blob deletion - var fileName = staff.ImageUrl.Split('/').LastOrDefault(); - if (!string.IsNullOrEmpty(fileName)) - { - await _azureBlobUploader.DeleteFileAsync(fileName); - } - } - catch (Exception ex) + // Extract filename from URL for Azure Blob deletion + var fileName = staff.ImageUrl.Split('/').LastOrDefault(); + if (!string.IsNullOrEmpty(fileName)) { - // Log error but continue - Console.WriteLine($"Error deleting photo: {ex.Message}"); + await _azureBlobUploader.DeleteFileAsync(fileName); } - - staff.ImageUrl = null; } + catch (Exception ex) + { + // Log error but continue + Console.WriteLine($"Error deleting photo: {ex.Message}"); + } + + staff.ImageUrl = null; } + } /// /// Deletes a staff member @@ -249,7 +250,7 @@ public async Task CreateBoard(Board board) await BoardDB.CreateBoardMember(_context, board); return RedirectToAction("IndexBoard"); } - + return View(board); } @@ -272,7 +273,7 @@ public async Task EditBoard(Board board) await BoardDB.EditBoardMember(_context, board); return RedirectToAction("IndexBoard"); } - + return View(board); } @@ -349,7 +350,7 @@ public async Task EditSteeringCommittee(SteeringCommittee steerin await SteeringCommitteeDB.EditSteeringCommittee(_context, steeringCommittee); return RedirectToAction("IndexSteeringCommittee"); } - + return View(steeringCommittee); } @@ -497,16 +498,16 @@ public async Task RenameNewsletter(int id) public async Task ConfirmRenameNewsletter(int id) { NewsletterFile? newsletter = await NewsletterFileDB.GetFileAsync(_context, id); - + if (newsletter != null) { - string oldName = newsletter.Name; + string oldName = newsletter.Name; string newName = Request.Form["Name"]; - + await NewsletterFileDB.RenameFileAsync(_context, id, newName); TempData["Message"] = $"Newsletter {oldName} renamed to {newName}"; } - + return RedirectToAction("UploadNewsletter"); } } From 2e585ab41d4b000fc0de4eedfecc6323e663bfd8 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Thu, 19 Feb 2026 15:37:58 -0800 Subject: [PATCH 03/19] Adjust heading margin and remove #pc2Logo padding Reduced #heading top margin and switched to em units for consistency. Removed #pc2Logo padding-bottom to eliminate extra space below the logo on large screens. --- PC2/wwwroot/css/site.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/PC2/wwwroot/css/site.css b/PC2/wwwroot/css/site.css index 1d049b2c..807d403c 100644 --- a/PC2/wwwroot/css/site.css +++ b/PC2/wwwroot/css/site.css @@ -118,7 +118,7 @@ main { #heading { width: 75%; - margin: 8% auto auto auto; + margin: 1em auto auto auto; padding-right: 10px; padding-left: 10px; } @@ -433,10 +433,6 @@ nav a { font-size: 16px; } - #pc2Logo { - padding-bottom: 1em; - } - #menu-bar * { width: 0; } From 5723921404aad626a44d8d4f0315edaae9c3b2a7 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Fri, 20 Feb 2026 17:12:55 -0800 Subject: [PATCH 04/19] Refactor People management: new controller & views Moved Staff, Board, and Steering Committee CRUD actions from AboutController to a new PeopleController. Updated dependency injection for image services. Staff photo upload now requires a file (no URL option). Rewrote and reorganized all related Razor views, removing generic People views. Updated navigation to use new controller routes. Cleaned up legacy code and improved UI consistency. --- PC2/Controllers/AboutController.cs | 384 +----------------- PC2/Controllers/PeopleController.cs | 302 ++++++++++---- PC2/Models/ViewModels/StaffViewModels.cs | 15 +- PC2/Views/About/DeleteBoard.cshtml | 38 -- PC2/Views/About/DeleteStaff.cshtml | 50 --- .../About/DeleteSteeringCommittee.cshtml | 32 -- PC2/Views/About/IndexBoard.cshtml | 46 --- PC2/Views/About/IndexStaff.cshtml | 58 --- PC2/Views/About/IndexSteeringCommittee.cshtml | 40 -- PC2/Views/People/Create.cshtml | 40 -- .../{About => People}/CreateBoard.cshtml | 19 +- .../{About => People}/CreateStaff.cshtml | 42 +- .../CreateSteeringCommittee.cshtml | 15 +- .../{Delete.cshtml => DeleteBoard.cshtml} | 26 +- PC2/Views/People/DeleteStaff.cshtml | 30 ++ .../People/DeleteSteeringCommittee.cshtml | 24 ++ PC2/Views/People/Details.cshtml | 30 -- PC2/Views/People/Edit.cshtml | 41 -- PC2/Views/{About => People}/EditBoard.cshtml | 17 +- PC2/Views/{About => People}/EditStaff.cshtml | 81 +--- .../EditSteeringCommittee.cshtml | 15 +- PC2/Views/People/Index.cshtml | 41 -- PC2/Views/People/IndexBoard.cshtml | 35 ++ PC2/Views/People/IndexStaff.cshtml | 39 ++ .../People/IndexSteeringCommittee.cshtml | 33 ++ PC2/Views/Shared/_Layout.cshtml | 13 +- 26 files changed, 467 insertions(+), 1039 deletions(-) delete mode 100644 PC2/Views/About/DeleteBoard.cshtml delete mode 100644 PC2/Views/About/DeleteStaff.cshtml delete mode 100644 PC2/Views/About/DeleteSteeringCommittee.cshtml delete mode 100644 PC2/Views/About/IndexBoard.cshtml delete mode 100644 PC2/Views/About/IndexStaff.cshtml delete mode 100644 PC2/Views/About/IndexSteeringCommittee.cshtml delete mode 100644 PC2/Views/People/Create.cshtml rename PC2/Views/{About => People}/CreateBoard.cshtml (81%) rename PC2/Views/{About => People}/CreateStaff.cshtml (72%) rename PC2/Views/{About => People}/CreateSteeringCommittee.cshtml (75%) rename PC2/Views/People/{Delete.cshtml => DeleteBoard.cshtml} (51%) create mode 100644 PC2/Views/People/DeleteStaff.cshtml create mode 100644 PC2/Views/People/DeleteSteeringCommittee.cshtml delete mode 100644 PC2/Views/People/Details.cshtml delete mode 100644 PC2/Views/People/Edit.cshtml rename PC2/Views/{About => People}/EditBoard.cshtml (78%) rename PC2/Views/{About => People}/EditStaff.cshtml (60%) rename PC2/Views/{About => People}/EditSteeringCommittee.cshtml (75%) delete mode 100644 PC2/Views/People/Index.cshtml create mode 100644 PC2/Views/People/IndexBoard.cshtml create mode 100644 PC2/Views/People/IndexStaff.cshtml create mode 100644 PC2/Views/People/IndexSteeringCommittee.cshtml diff --git a/PC2/Controllers/AboutController.cs b/PC2/Controllers/AboutController.cs index eda9a9b2..6d1558b1 100644 --- a/PC2/Controllers/AboutController.cs +++ b/PC2/Controllers/AboutController.cs @@ -1,10 +1,8 @@ -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.EntityFrameworkCore; using PC2.Data; using PC2.Models; -using PC2.Models.ViewModels; using PC2.Services; namespace PC2.Controllers; @@ -14,371 +12,11 @@ public class AboutController : Controller { private readonly ApplicationDbContext _context; private readonly AzureBlobUploader _azureBlobUploader; - private readonly ImageService _imageService; - // Iwebhost environment is used to get the path to the wwwroot folder - public AboutController(ApplicationDbContext context, AzureBlobUploader azureBlobUploader, ImageService imageService) + public AboutController(ApplicationDbContext context, AzureBlobUploader azureBlobUploader) { _context = context; _azureBlobUploader = azureBlobUploader; - _imageService = imageService; - } - - public async Task IndexStaff() - { - return View(await StaffDB.GetAllStaffForEditing(_context)); - } - - /// - /// Creates a staff member - /// - /// - [HttpGet] - public IActionResult CreateStaff() - { - return View(new CreateStaffViewModel()); - } - - [HttpPost] - public async Task CreateStaff(CreateStaffViewModel model) - { - if (ModelState.IsValid) - { - var staff = new Staff - { - Name = model.Name, - Title = model.Title, - Phone = model.Phone, - Extension = model.Extension, - Email = model.Email, - PriorityOrder = model.PriorityOrder - }; - - // Handle image upload - await HandleImageUpload(model.PhotoFile, model.ImageUrl, staff); - - await StaffDB.AddStaff(_context, staff); - return RedirectToAction("IndexStaff"); - } - return View(model); - } - - /// - /// Edits a staff member - /// - /// The id for the staff member - /// - [HttpGet] - public async Task EditStaff(int id) - { - var staff = await StaffDB.GetStaffMember(_context, id); - if (staff == null) - { - return NotFound(); - } - - var model = new EditStaffViewModel - { - ID = staff.ID, - Name = staff.Name, - Title = staff.Title, - Phone = staff.Phone, - Extension = staff.Extension, - Email = staff.Email, - CurrentImageUrl = staff.ImageUrl, - ImageUrl = staff.ImageUrl, - PriorityOrder = staff.PriorityOrder - }; - - return View(model); - } - - [HttpPost] - public async Task EditStaff(EditStaffViewModel model) - { - if (ModelState.IsValid) - { - var staff = await StaffDB.GetStaffMember(_context, model.ID); - if (staff == null) - { - return NotFound(); - } - - // Update basic properties - staff.Name = model.Name; - staff.Title = model.Title; - staff.Phone = model.Phone; - staff.Extension = model.Extension; - staff.Email = model.Email; - staff.PriorityOrder = model.PriorityOrder; - - // Handle photo removal - if (model.RemovePhoto) - { - await RemoveStaffPhoto(staff); - } - // Handle new photo upload - else if (model.PhotoFile != null || !string.IsNullOrEmpty(model.ImageUrl)) - { - await HandleImageUpload(model.PhotoFile, model.ImageUrl, staff, model.ID); - } - - await StaffDB.SaveChanges(_context, staff); - return RedirectToAction("IndexStaff"); - } - - // If model is invalid, reload current image URL for display - model.CurrentImageUrl = (await StaffDB.GetStaffMember(_context, model.ID))?.ImageUrl; - return View(model); - } - - private async Task HandleImageUpload(IFormFile? photoFile, string? imageUrl, Staff staff, int? staffId = null) - { - try - { - // Priority: uploaded file over URL - if (photoFile != null && photoFile.Length > 0) - { - // Validate image file - if (!ImageService.IsValidImageFile(photoFile)) - { - throw new InvalidOperationException("Please upload a valid image file (JPEG, PNG, GIF, or BMP)."); - } - - // Delete old photo if updating existing staff - if (staffId.HasValue && !string.IsNullOrEmpty(staff.ImageUrl)) - { - await RemoveStaffPhoto(staff); - } - - // Generate safe filename - var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, staffId ?? 0); - - // Resize image - using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream()); - - // Create IFormFile from resized image - var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); - - // Upload to Azure Blob - staff.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); - } - else if (!string.IsNullOrEmpty(imageUrl) && Uri.IsWellFormedUriString(imageUrl, UriKind.Absolute)) - { - // Use provided URL - staff.ImageUrl = imageUrl; - } - } - catch (Exception ex) - { - // Log error and continue without setting image - // In a production app, you might want to add this error to ModelState - Console.WriteLine($"Error handling image upload: {ex.Message}"); - } - } - - private async Task RemoveStaffPhoto(Staff staff) - { - if (!string.IsNullOrEmpty(staff.ImageUrl)) - { - try - { - // Extract filename from URL for Azure Blob deletion - var fileName = staff.ImageUrl.Split('/').LastOrDefault(); - if (!string.IsNullOrEmpty(fileName)) - { - await _azureBlobUploader.DeleteFileAsync(fileName); - } - } - catch (Exception ex) - { - // Log error but continue - Console.WriteLine($"Error deleting photo: {ex.Message}"); - } - - staff.ImageUrl = null; - } - } - - /// - /// Deletes a staff member - /// - /// The id of the staff member - /// - [HttpGet] - public async Task DeleteStaff(int id) - { - return View(await StaffDB.GetStaffMember(_context, id)); - } - - [HttpPost] - [ActionName("DeleteStaff")] - public async Task ConfirmDeleteStaff(int id) - { - Staff? staff = await StaffDB.GetStaffMember(_context, id); - - if (staff == null) - { - // If staff member is not found - return NotFound(); // Return a NotFound result - } - - await StaffDB.Delete(_context, staff); - return RedirectToAction("IndexStaff"); - } - - public async Task IndexBoard() - { - return View(await BoardDB.GetAllBoardMembersForEditing(_context)); - } - - /// - /// Creates a board member - /// - /// - [HttpGet] - public IActionResult CreateBoard() - { - return View(); - } - - [HttpPost] - public async Task CreateBoard(Board board) - { - if (ModelState.IsValid) - { - await BoardDB.CreateBoardMember(_context, board); - return RedirectToAction("IndexBoard"); - } - - return View(board); - } - - /// - /// Edits a board member - /// - /// The id of the board member - /// - [HttpGet] - public async Task EditBoard(int id) - { - return View(await BoardDB.GetBoardMember(_context, id)); - } - - [HttpPost] - public async Task EditBoard(Board board) - { - if (ModelState.IsValid) - { - await BoardDB.EditBoardMember(_context, board); - return RedirectToAction("IndexBoard"); - } - - return View(board); - } - - /// - /// Deletes a board member - /// - /// The id of the board member - /// - [HttpGet] - public async Task DeleteBoard(int id) - { - return View(await BoardDB.GetBoardMember(_context, id)); - } - - [HttpPost] - [ActionName("DeleteBoard")] - public async Task ConfirmDeleteBoard(int id) - { - Board? board = await BoardDB.GetBoardMember(_context, id); - - if (board == null) - { - // If board member is not found - return NotFound(); // Return a NotFound result - } - - await BoardDB.Delete(_context, board); - return RedirectToAction("IndexBoard"); - } - - public async Task IndexSteeringCommittee() - { - return View(await SteeringCommitteeDB.GetAllSteeringCommittee(_context)); - } - - /// - /// Creates a steering committee member - /// - /// - [HttpGet] - public IActionResult CreateSteeringCommittee() - { - return View(); - } - - [HttpPost] - public async Task CreateSteeringCommittee(SteeringCommittee steeringCommittee) - { - if (ModelState.IsValid) - { - await SteeringCommitteeDB.Create(_context, steeringCommittee); - return RedirectToAction("IndexSteeringCommittee"); - } - - return View(steeringCommittee); - } - - /// - /// Gets a steering committee member by id - /// - /// The id of the steering committee member - /// - [HttpGet] - public async Task EditSteeringCommittee(int id) - { - return View(await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id)); - } - - [HttpPost] - public async Task EditSteeringCommittee(SteeringCommittee steeringCommittee) - { - if (ModelState.IsValid) - { - await SteeringCommitteeDB.EditSteeringCommittee(_context, steeringCommittee); - return RedirectToAction("IndexSteeringCommittee"); - } - - return View(steeringCommittee); - } - - /// - /// Deletes a steering committee member by id - /// - /// The id of the member - /// - [HttpGet] - public async Task DeleteSteeringCommittee(int id) - { - return View(await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id)); - } - - [HttpPost] - [ActionName("DeleteSteeringCommittee")] - public async Task ConfirmDeleteSteeringCommittee(int id) - { - SteeringCommittee? steeringCommittee = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id); - - if (steeringCommittee == null) - { - // If the steering committee member is not found - return NotFound(); // Return a NotFound result - } - - await SteeringCommitteeDB.Delete(_context, steeringCommittee); - return RedirectToAction("IndexSteeringCommittee"); } public async Task HousingProgramData() @@ -391,7 +29,7 @@ public async Task HousingProgramData() public async Task HousingProgramData(HousingProgram model) { if (ModelState.IsValid) - { // if all the data is valid, update the database + { HousingProgram entry = new() { HouseHoldSize = model.HouseHoldSize, @@ -403,9 +41,8 @@ public async Task HousingProgramData(HousingProgram model) TempData["Message"] = $"Entry for Household size {entry.HouseHoldSize} updated Successfully"; return RedirectToAction("HousingProgramData"); } - // If model state is not valid, return the view with the model to show validation errors + List data = await _context.HousingProgram.OrderBy(hp => hp.HouseHoldSize).ToListAsync(); - // keep the data from user's input data[data.FindIndex(hp => hp.HouseHoldSize == model.HouseHoldSize)].MaximumIncome = model.MaximumIncome; TempData["Message"] = $"Maximum income must be a non - negative number at HouseHold size {model.HouseHoldSize}."; return View(data); @@ -425,7 +62,6 @@ public async Task UploadNewsletter(IFormFile userFile) { try { - // Upload the file to BLOB storage string filePath = await _azureBlobUploader.UploadFileAsync(userFile, userFile.FileName); TempData["Message"] = $"{userFile.FileName} uploaded successfully"; @@ -435,7 +71,6 @@ public async Task UploadNewsletter(IFormFile userFile) Location = filePath, }; - // add newsletterFile to the DB await NewsletterFileDB.AddAsync(_context, newsLetterFile); } catch (Exception ex) @@ -462,17 +97,14 @@ public async Task ConfirmDeleteNewsletter(int id) try { NewsletterFile? newsletter = await NewsletterFileDB.GetFileAsync(_context, id); - // delete actual file from wwwroot/PDF/focus-newsletters if (newsletter != null) { - // actual file name is never changed and object location is never changed when renaming - string? originalFile = newsletter.Location?.Split('/').LastOrDefault(); // Get the file name from the end of the URL + string? originalFile = newsletter.Location?.Split('/').LastOrDefault(); if (originalFile != null) { - bool isDeleted = await _azureBlobUploader.DeleteFileAsync(originalFile); // Delete the file from Azure Blob Storage + bool isDeleted = await _azureBlobUploader.DeleteFileAsync(originalFile); } - // remove from DB await NewsletterFileDB.DeleteAsync(_context, newsletter.NewsletterId); TempData["Message"] = $"{newsletter.Name} deleted successfully"; } @@ -488,9 +120,7 @@ public async Task ConfirmDeleteNewsletter(int id) [HttpGet] public async Task RenameNewsletter(int id) { - { - return View(await NewsletterFileDB.GetFileAsync(_context, id)); - } + return View(await NewsletterFileDB.GetFileAsync(_context, id)); } [HttpPost] diff --git a/PC2/Controllers/PeopleController.cs b/PC2/Controllers/PeopleController.cs index 5936ecff..e866cf08 100644 --- a/PC2/Controllers/PeopleController.cs +++ b/PC2/Controllers/PeopleController.cs @@ -1,8 +1,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using PC2.Data; using PC2.Models; +using PC2.Models.ViewModels; +using PC2.Services; namespace PC2.Controllers { @@ -10,139 +11,296 @@ namespace PC2.Controllers public class PeopleController : Controller { private readonly ApplicationDbContext _context; + private readonly AzureBlobUploader _azureBlobUploader; + private readonly ImageService _imageService; - public PeopleController(ApplicationDbContext context) + public PeopleController(ApplicationDbContext context, AzureBlobUploader azureBlobUploader, ImageService imageService) { _context = context; + _azureBlobUploader = azureBlobUploader; + _imageService = imageService; } - // GET: People - public async Task Index() + // ---- Staff ---- + + public async Task IndexStaff() + { + return View(await StaffDB.GetAllStaffForEditing(_context)); + } + + [HttpGet] + public IActionResult CreateStaff() { - var people = await _context.People.OrderBy(p => p.PriorityOrder).ThenBy(p => p.Name).ToListAsync(); - return View(people); + return View(new CreateStaffViewModel()); + } + + [HttpPost] + public async Task CreateStaff(CreateStaffViewModel model) + { + if (ModelState.IsValid) + { + var staff = new Staff + { + Name = model.Name, + Title = model.Title, + Phone = model.Phone, + Extension = model.Extension, + Email = model.Email, + PriorityOrder = model.PriorityOrder + }; + + await HandlePhotoUpload(model.PhotoFile, staff); + await StaffDB.AddStaff(_context, staff); + return RedirectToAction(nameof(IndexStaff)); + } + return View(model); } - // GET: People/Details/5 - public async Task Details(int? id) + [HttpGet] + public async Task EditStaff(int id) { - if (id == null) + var staff = await StaffDB.GetStaffMember(_context, id); + if (staff == null) { return NotFound(); } - var person = await _context.People.FirstOrDefaultAsync(m => m.ID == id); - if (person == null) + var model = new EditStaffViewModel + { + ID = staff.ID, + Name = staff.Name, + Title = staff.Title, + Phone = staff.Phone, + Extension = staff.Extension, + Email = staff.Email, + CurrentImageUrl = staff.ImageUrl, + PriorityOrder = staff.PriorityOrder + }; + + return View(model); + } + + [HttpPost] + public async Task EditStaff(EditStaffViewModel model) + { + if (ModelState.IsValid) + { + var staff = await StaffDB.GetStaffMember(_context, model.ID); + if (staff == null) + { + return NotFound(); + } + + staff.Name = model.Name; + staff.Title = model.Title; + staff.Phone = model.Phone; + staff.Extension = model.Extension; + staff.Email = model.Email; + staff.PriorityOrder = model.PriorityOrder; + + if (model.RemovePhoto) + { + await RemoveStaffPhoto(staff); + } + else if (model.PhotoFile != null) + { + await HandlePhotoUpload(model.PhotoFile, staff, model.ID); + } + + await StaffDB.SaveChanges(_context, staff); + return RedirectToAction(nameof(IndexStaff)); + } + + model.CurrentImageUrl = (await StaffDB.GetStaffMember(_context, model.ID))?.ImageUrl; + return View(model); + } + + [HttpGet] + public async Task DeleteStaff(int id) + { + return View(await StaffDB.GetStaffMember(_context, id)); + } + + [HttpPost, ActionName("DeleteStaff")] + public async Task ConfirmDeleteStaff(int id) + { + Staff? staff = await StaffDB.GetStaffMember(_context, id); + if (staff == null) { return NotFound(); } - return View(person); + await StaffDB.Delete(_context, staff); + return RedirectToAction(nameof(IndexStaff)); + } + + // ---- Board ---- + + public async Task IndexBoard() + { + return View(await BoardDB.GetAllBoardMembersForEditing(_context)); } - // GET: People/Create - public IActionResult Create() + [HttpGet] + public IActionResult CreateBoard() { return View(); } - // POST: People/Create [HttpPost] - [ValidateAntiForgeryToken] - public async Task Create([Bind("Name,Title,PriorityOrder")] People person) + public async Task CreateBoard(Board board) { if (ModelState.IsValid) { - _context.Add(person); - await _context.SaveChangesAsync(); - return RedirectToAction(nameof(Index)); + await BoardDB.CreateBoardMember(_context, board); + return RedirectToAction(nameof(IndexBoard)); } - return View(person); + return View(board); + } + + [HttpGet] + public async Task EditBoard(int id) + { + return View(await BoardDB.GetBoardMember(_context, id)); } - // GET: People/Edit/5 - public async Task Edit(int? id) + [HttpPost] + public async Task EditBoard(Board board) { - if (id == null) + if (ModelState.IsValid) { - return NotFound(); + await BoardDB.EditBoardMember(_context, board); + return RedirectToAction(nameof(IndexBoard)); } + return View(board); + } - var person = await _context.People.FindAsync(id); - if (person == null) + [HttpGet] + public async Task DeleteBoard(int id) + { + return View(await BoardDB.GetBoardMember(_context, id)); + } + + [HttpPost, ActionName("DeleteBoard")] + public async Task ConfirmDeleteBoard(int id) + { + Board? board = await BoardDB.GetBoardMember(_context, id); + if (board == null) { return NotFound(); } - return View(person); + + await BoardDB.Delete(_context, board); + return RedirectToAction(nameof(IndexBoard)); + } + + // ---- Steering Committee ---- + + public async Task IndexSteeringCommittee() + { + return View(await SteeringCommitteeDB.GetAllSteeringCommittee(_context)); + } + + [HttpGet] + public IActionResult CreateSteeringCommittee() + { + return View(); } - // POST: People/Edit/5 [HttpPost] - [ValidateAntiForgeryToken] - public async Task Edit(int id, [Bind("ID,Name,Title,PriorityOrder")] People person) + public async Task CreateSteeringCommittee(SteeringCommittee steeringCommittee) { - if (id != person.ID) + if (ModelState.IsValid) { - return NotFound(); + await SteeringCommitteeDB.Create(_context, steeringCommittee); + return RedirectToAction(nameof(IndexSteeringCommittee)); } + return View(steeringCommittee); + } + + [HttpGet] + public async Task EditSteeringCommittee(int id) + { + return View(await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id)); + } + [HttpPost] + public async Task EditSteeringCommittee(SteeringCommittee steeringCommittee) + { if (ModelState.IsValid) { - try - { - _context.Update(person); - await _context.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException) - { - if (!PersonExists(person.ID)) - { - return NotFound(); - } - else - { - throw; - } - } - return RedirectToAction(nameof(Index)); + await SteeringCommitteeDB.EditSteeringCommittee(_context, steeringCommittee); + return RedirectToAction(nameof(IndexSteeringCommittee)); } - return View(person); + return View(steeringCommittee); } - // GET: People/Delete/5 - public async Task Delete(int? id) + [HttpGet] + public async Task DeleteSteeringCommittee(int id) { - if (id == null) - { - return NotFound(); - } + return View(await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id)); + } - var person = await _context.People.FirstOrDefaultAsync(m => m.ID == id); - if (person == null) + [HttpPost, ActionName("DeleteSteeringCommittee")] + public async Task ConfirmDeleteSteeringCommittee(int id) + { + SteeringCommittee? steeringCommittee = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id); + if (steeringCommittee == null) { return NotFound(); } - return View(person); + await SteeringCommitteeDB.Delete(_context, steeringCommittee); + return RedirectToAction(nameof(IndexSteeringCommittee)); } - // POST: People/Delete/5 - [HttpPost, ActionName("Delete")] - [ValidateAntiForgeryToken] - public async Task DeleteConfirmed(int id) + // ---- Photo helpers ---- + + private async Task HandlePhotoUpload(IFormFile? photoFile, Staff staff, int? staffId = null) { - var person = await _context.People.FindAsync(id); - if (person != null) + if (photoFile == null || photoFile.Length == 0) return; + + try + { + if (!ImageService.IsValidImageFile(photoFile)) + { + throw new InvalidOperationException("Please upload a valid image file (JPEG, PNG, GIF, or BMP)."); + } + + if (staffId.HasValue && !string.IsNullOrEmpty(staff.ImageUrl)) + { + await RemoveStaffPhoto(staff); + } + + var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, staffId ?? 0); + using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream()); + var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); + staff.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); + } + catch (Exception ex) { - _context.People.Remove(person); - await _context.SaveChangesAsync(); + Console.WriteLine($"Error handling image upload: {ex.Message}"); } - return RedirectToAction(nameof(Index)); } - private bool PersonExists(int id) + private async Task RemoveStaffPhoto(Staff staff) { - return _context.People.Any(e => e.ID == id); + if (string.IsNullOrEmpty(staff.ImageUrl)) return; + + try + { + var fileName = staff.ImageUrl.Split('/').LastOrDefault(); + if (!string.IsNullOrEmpty(fileName)) + { + await _azureBlobUploader.DeleteFileAsync(fileName); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting photo: {ex.Message}"); + } + + staff.ImageUrl = null; } } -} \ No newline at end of file +} diff --git a/PC2/Models/ViewModels/StaffViewModels.cs b/PC2/Models/ViewModels/StaffViewModels.cs index 8aa03d62..d0e9f954 100644 --- a/PC2/Models/ViewModels/StaffViewModels.cs +++ b/PC2/Models/ViewModels/StaffViewModels.cs @@ -39,16 +39,10 @@ public class CreateStaffViewModel /// /// Photo file upload /// + [Required(ErrorMessage = "Please upload a photo.")] [Display(Name = "Photo")] public IFormFile? PhotoFile { get; set; } - /// - /// Alternative: Photo URL (if not uploading a file) - /// - [DataType(DataType.Url)] - [Display(Name = "Photo URL (if not uploading file)")] - public string? ImageUrl { get; set; } - /// /// Used to sort People by their display priority. Lower number /// is a higher priority @@ -105,13 +99,6 @@ public class EditStaffViewModel [Display(Name = "New Photo")] public IFormFile? PhotoFile { get; set; } - /// - /// Alternative: Photo URL (if not uploading a file) - /// - [DataType(DataType.Url)] - [Display(Name = "Photo URL (if not uploading file)")] - public string? ImageUrl { get; set; } - /// /// Whether to remove the current photo /// diff --git a/PC2/Views/About/DeleteBoard.cshtml b/PC2/Views/About/DeleteBoard.cshtml deleted file mode 100644 index 1f20c6e9..00000000 --- a/PC2/Views/About/DeleteBoard.cshtml +++ /dev/null @@ -1,38 +0,0 @@ -@model PC2.Models.Board - -@{ - ViewData["Title"] = "Delete Board Member"; -} - -

    Delete @Model.Name

    - -

    Are you sure you want to delete @Model.Name?

    -
    -
    -
    -
    - @Html.DisplayNameFor(model => model.Name) -
    -
    - @Html.DisplayFor(model => model.Name) -
    -
    - @Html.DisplayNameFor(model => model.Title) -
    -
    - @Html.DisplayFor(model => model.Title) -
    -
    - @Html.DisplayNameFor(model => model.MembershipStart) -
    -
    - @Html.DisplayFor(model => model.MembershipStart) -
    -
    - -
    - - | - Back to List -
    -
    diff --git a/PC2/Views/About/DeleteStaff.cshtml b/PC2/Views/About/DeleteStaff.cshtml deleted file mode 100644 index f6fd3fa5..00000000 --- a/PC2/Views/About/DeleteStaff.cshtml +++ /dev/null @@ -1,50 +0,0 @@ -@model PC2.Models.Staff - -@{ - ViewData["Title"] = "Delete Staff Member"; -} - -

    Delete @Model.Name

    - -

    Are you sure you want to delete @Model.Name?

    -
    -
    -
    -
    - @Html.DisplayNameFor(model => model.Name) -
    -
    - @Html.DisplayFor(model => model.Name) -
    -
    - @Html.DisplayNameFor(model => model.Title) -
    -
    - @Html.DisplayFor(model => model.Title) -
    -
    - @Html.DisplayNameFor(model => model.Phone) -
    -
    - @Html.DisplayFor(model => model.Phone) -
    -
    - @Html.DisplayNameFor(model => model.Extension) -
    -
    - @Html.DisplayFor(model => model.Extension) -
    -
    - @Html.DisplayNameFor(model => model.Email) -
    -
    - @Html.DisplayFor(model => model.Email) -
    -
    - -
    - - | - Back to List -
    -
    diff --git a/PC2/Views/About/DeleteSteeringCommittee.cshtml b/PC2/Views/About/DeleteSteeringCommittee.cshtml deleted file mode 100644 index 1aebc920..00000000 --- a/PC2/Views/About/DeleteSteeringCommittee.cshtml +++ /dev/null @@ -1,32 +0,0 @@ -@model PC2.Models.SteeringCommittee - -@{ - ViewData["Title"] = "Delete Steering Committee Member"; -} - -

    Delete @Model.Name

    - -

    Are you sure you want to delete @Model.Name?

    -
    -
    -
    -
    - @Html.DisplayNameFor(model => model.Name) -
    -
    - @Html.DisplayFor(model => model.Name) -
    -
    - @Html.DisplayNameFor(model => model.Title) -
    -
    - @Html.DisplayFor(model => model.Title) -
    -
    - -
    - - | - Back to List -
    -
    diff --git a/PC2/Views/About/IndexBoard.cshtml b/PC2/Views/About/IndexBoard.cshtml deleted file mode 100644 index 364e5b12..00000000 --- a/PC2/Views/About/IndexBoard.cshtml +++ /dev/null @@ -1,46 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Board Members"; -} - -

    @ViewData["Title"]

    - -

    - Create New Board Member -

    - - - - - - - - - - -@foreach (var item in Model) { - - - - - - -} - -
    - @Html.DisplayNameFor(model => model.Name) - - @Html.DisplayNameFor(model => model.Title) - - Start of Membership -
    - @Html.DisplayFor(modelItem => item.Name) - - @Html.DisplayFor(modelItem => item.Title) - - Member since @Html.DisplayFor(modelItem => item.MembershipStart) - - Edit | - Delete -
    diff --git a/PC2/Views/About/IndexStaff.cshtml b/PC2/Views/About/IndexStaff.cshtml deleted file mode 100644 index 75d651de..00000000 --- a/PC2/Views/About/IndexStaff.cshtml +++ /dev/null @@ -1,58 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Staff Members"; -} - -

    @ViewData["Title"]

    - -

    - Create New Staff Member -

    - - - - - - - - - - - - -@foreach (var item in Model) { - - - - - - - - -} - -
    - @Html.DisplayNameFor(model => model.Name) - - @Html.DisplayNameFor(model => model.Title) - - @Html.DisplayNameFor(model => model.Phone) - - @Html.DisplayNameFor(model => model.Extension) - - @Html.DisplayNameFor(model => model.Email) -
    - @Html.DisplayFor(modelItem => item.Name) - - @Html.DisplayFor(modelItem => item.Title) - - @Html.DisplayFor(modelItem => item.Phone) - - @Html.DisplayFor(modelItem => item.Extension) - - @Html.DisplayFor(modelItem => item.Email) - - Edit | - Delete -
    diff --git a/PC2/Views/About/IndexSteeringCommittee.cshtml b/PC2/Views/About/IndexSteeringCommittee.cshtml deleted file mode 100644 index aa61436d..00000000 --- a/PC2/Views/About/IndexSteeringCommittee.cshtml +++ /dev/null @@ -1,40 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Steering Committee Members"; -} - -

    @ViewData["Title"]

    - -

    - Create New -

    - - - - - - - - - -@foreach (var item in Model) { - - - - - -} - -
    - @Html.DisplayNameFor(model => model.Name) - - @Html.DisplayNameFor(model => model.Title) -
    - @Html.DisplayFor(modelItem => item.Name) - - @Html.DisplayFor(modelItem => item.Title) - - Edit | - Delete -
    diff --git a/PC2/Views/People/Create.cshtml b/PC2/Views/People/Create.cshtml deleted file mode 100644 index 7d7e55c7..00000000 --- a/PC2/Views/People/Create.cshtml +++ /dev/null @@ -1,40 +0,0 @@ -@model PC2.Models.People - -@{ - ViewData["Title"] = "Create Person"; -} - -

    @ViewData["Title"]

    - -
    -
    -
    -
    -
    -
    - - - -
    -
    - - - -
    -
    - - - - Lower numbers have higher priority (0 = highest) -
    -
    - - Cancel -
    -
    -
    -
    - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} -} \ No newline at end of file diff --git a/PC2/Views/About/CreateBoard.cshtml b/PC2/Views/People/CreateBoard.cshtml similarity index 81% rename from PC2/Views/About/CreateBoard.cshtml rename to PC2/Views/People/CreateBoard.cshtml index 49fb74f7..e72a8da5 100644 --- a/PC2/Views/About/CreateBoard.cshtml +++ b/PC2/Views/People/CreateBoard.cshtml @@ -1,32 +1,31 @@ -@model PC2.Models.Board +@model PC2.Models.Board @{ ViewData["Title"] = "Create Board Member"; }

    @ViewData["Title"]

    -
    -
    +
    -
    +
    -
    +
    -
    +
    + Cancel
    - - +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/PC2/Views/About/CreateStaff.cshtml b/PC2/Views/People/CreateStaff.cshtml similarity index 72% rename from PC2/Views/About/CreateStaff.cshtml rename to PC2/Views/People/CreateStaff.cshtml index e4f40e16..e97f36bc 100644 --- a/PC2/Views/About/CreateStaff.cshtml +++ b/PC2/Views/People/CreateStaff.cshtml @@ -1,4 +1,4 @@ -@model PC2.Models.ViewModels.CreateStaffViewModel +@model PC2.Models.ViewModels.CreateStaffViewModel @{ ViewData["Title"] = "Create Staff Member"; @@ -10,19 +10,19 @@
    - +
    - +
    - +
    @@ -39,7 +39,7 @@
    - +
    @@ -53,25 +53,12 @@
    - + Upload a photo file (JPEG, PNG, GIF, or BMP). Large images will be automatically resized.
    - -
    - OR -
    - -
    - - - - - Enter a URL to an existing photo online - -
    @@ -83,7 +70,7 @@
    - +
    Cancel @@ -94,19 +81,4 @@ @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} - } - diff --git a/PC2/Views/About/CreateSteeringCommittee.cshtml b/PC2/Views/People/CreateSteeringCommittee.cshtml similarity index 75% rename from PC2/Views/About/CreateSteeringCommittee.cshtml rename to PC2/Views/People/CreateSteeringCommittee.cshtml index c07e581a..22299b61 100644 --- a/PC2/Views/About/CreateSteeringCommittee.cshtml +++ b/PC2/Views/People/CreateSteeringCommittee.cshtml @@ -1,34 +1,33 @@ -@model PC2.Models.SteeringCommittee +@model PC2.Models.SteeringCommittee @{ ViewData["Title"] = "Create Steering Committee Member"; }

    @ViewData["Title"]

    -
    -
    +
    -
    +
    + Cancel
    - - +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/PC2/Views/People/Delete.cshtml b/PC2/Views/People/DeleteBoard.cshtml similarity index 51% rename from PC2/Views/People/Delete.cshtml rename to PC2/Views/People/DeleteBoard.cshtml index 7f6c4eb6..4e45cf1c 100644 --- a/PC2/Views/People/Delete.cshtml +++ b/PC2/Views/People/DeleteBoard.cshtml @@ -1,32 +1,26 @@ -@model PC2.Models.People +@model PC2.Models.Board @{ - ViewData["Title"] = "Delete Person"; + ViewData["Title"] = "Delete Board Member"; } -

    @ViewData["Title"]

    +

    Delete @Model.Name

    -

    Are you sure you want to delete this person?

    +

    Are you sure you want to delete @Model.Name?

    -

    @Model.Name


    @Html.DisplayNameFor(model => model.Name)
    @Html.DisplayFor(model => model.Name)
    -
    @Html.DisplayNameFor(model => model.Title)
    @Html.DisplayFor(model => model.Title)
    - -
    @Html.DisplayNameFor(model => model.PriorityOrder)
    -
    @Html.DisplayFor(model => model.PriorityOrder)
    - -
    Type
    -
    @Model.GetType().Name
    +
    @Html.DisplayNameFor(model => model.MembershipStart)
    +
    @Html.DisplayFor(model => model.MembershipStart)
    -
    + - - Cancel + | + Back to List
    -
    \ No newline at end of file +
    diff --git a/PC2/Views/People/DeleteStaff.cshtml b/PC2/Views/People/DeleteStaff.cshtml new file mode 100644 index 00000000..4f687d98 --- /dev/null +++ b/PC2/Views/People/DeleteStaff.cshtml @@ -0,0 +1,30 @@ +@model PC2.Models.Staff + +@{ + ViewData["Title"] = "Delete Staff Member"; +} + +

    Delete @Model.Name

    + +

    Are you sure you want to delete @Model.Name?

    +
    +
    +
    +
    @Html.DisplayNameFor(model => model.Name)
    +
    @Html.DisplayFor(model => model.Name)
    +
    @Html.DisplayNameFor(model => model.Title)
    +
    @Html.DisplayFor(model => model.Title)
    +
    @Html.DisplayNameFor(model => model.Phone)
    +
    @Html.DisplayFor(model => model.Phone)
    +
    @Html.DisplayNameFor(model => model.Extension)
    +
    @Html.DisplayFor(model => model.Extension)
    +
    @Html.DisplayNameFor(model => model.Email)
    +
    @Html.DisplayFor(model => model.Email)
    +
    + +
    + + | + Back to List +
    +
    diff --git a/PC2/Views/People/DeleteSteeringCommittee.cshtml b/PC2/Views/People/DeleteSteeringCommittee.cshtml new file mode 100644 index 00000000..e9242c6d --- /dev/null +++ b/PC2/Views/People/DeleteSteeringCommittee.cshtml @@ -0,0 +1,24 @@ +@model PC2.Models.SteeringCommittee + +@{ + ViewData["Title"] = "Delete Steering Committee Member"; +} + +

    Delete @Model.Name

    + +

    Are you sure you want to delete @Model.Name?

    +
    +
    +
    +
    @Html.DisplayNameFor(model => model.Name)
    +
    @Html.DisplayFor(model => model.Name)
    +
    @Html.DisplayNameFor(model => model.Title)
    +
    @Html.DisplayFor(model => model.Title)
    +
    + +
    + + | + Back to List +
    +
    diff --git a/PC2/Views/People/Details.cshtml b/PC2/Views/People/Details.cshtml deleted file mode 100644 index 426c1a64..00000000 --- a/PC2/Views/People/Details.cshtml +++ /dev/null @@ -1,30 +0,0 @@ -@model PC2.Models.People - -@{ - ViewData["Title"] = "Person Details"; -} - -

    @ViewData["Title"]

    - -
    -

    @Model.Name

    -
    -
    -
    @Html.DisplayNameFor(model => model.Name)
    -
    @Html.DisplayFor(model => model.Name)
    - -
    @Html.DisplayNameFor(model => model.Title)
    -
    @Html.DisplayFor(model => model.Title)
    - -
    @Html.DisplayNameFor(model => model.PriorityOrder)
    -
    @Html.DisplayFor(model => model.PriorityOrder)
    - -
    Type
    -
    @Model.GetType().Name
    -
    -
    - - \ No newline at end of file diff --git a/PC2/Views/People/Edit.cshtml b/PC2/Views/People/Edit.cshtml deleted file mode 100644 index 17f78d62..00000000 --- a/PC2/Views/People/Edit.cshtml +++ /dev/null @@ -1,41 +0,0 @@ -@model PC2.Models.People - -@{ - ViewData["Title"] = "Edit Person"; -} - -

    Edit @Model.Name

    - -
    -
    -
    -
    -
    - -
    - - - -
    -
    - - - -
    -
    - - - - Lower numbers have higher priority (0 = highest) -
    -
    - - Cancel -
    -
    -
    -
    - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} -} \ No newline at end of file diff --git a/PC2/Views/About/EditBoard.cshtml b/PC2/Views/People/EditBoard.cshtml similarity index 78% rename from PC2/Views/About/EditBoard.cshtml rename to PC2/Views/People/EditBoard.cshtml index c33a5408..92bb335f 100644 --- a/PC2/Views/About/EditBoard.cshtml +++ b/PC2/Views/People/EditBoard.cshtml @@ -1,40 +1,39 @@ -@model PC2.Models.Board +@model PC2.Models.Board @{ ViewData["Title"] = "Edit Board Member"; }

    Edit @Model.Name

    -
    -
    +
    -
    +
    -
    +
    + Cancel
    - - +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/PC2/Views/About/EditStaff.cshtml b/PC2/Views/People/EditStaff.cshtml similarity index 60% rename from PC2/Views/About/EditStaff.cshtml rename to PC2/Views/People/EditStaff.cshtml index 01502bc3..420ca7c5 100644 --- a/PC2/Views/About/EditStaff.cshtml +++ b/PC2/Views/People/EditStaff.cshtml @@ -1,30 +1,29 @@ -@model PC2.Models.ViewModels.EditStaffViewModel +@model PC2.Models.ViewModels.EditStaffViewModel @{ ViewData["Title"] = "Edit Staff Member"; }

    Edit @Model.Name

    -
    - +
    - +
    - +
    @@ -41,7 +40,7 @@
    - +
    @@ -55,7 +54,7 @@
    @if (!string.IsNullOrEmpty(Model.CurrentImageUrl)) { -
    +
    @Model.Name @@ -64,19 +63,11 @@
    - -
    - -
    -
    - Update Photo: -
    +
    } -
    +
    @@ -84,19 +75,6 @@ Upload a new photo file (JPEG, PNG, GIF, or BMP). Large images will be automatically resized.
    - -
    - OR -
    - -
    - - - - - Enter a URL to an existing photo online - -
    @@ -108,7 +86,7 @@
    - +
    Cancel @@ -120,46 +98,15 @@ @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} } - diff --git a/PC2/Views/About/EditSteeringCommittee.cshtml b/PC2/Views/People/EditSteeringCommittee.cshtml similarity index 75% rename from PC2/Views/About/EditSteeringCommittee.cshtml rename to PC2/Views/People/EditSteeringCommittee.cshtml index 432cac51..6e31e4f7 100644 --- a/PC2/Views/About/EditSteeringCommittee.cshtml +++ b/PC2/Views/People/EditSteeringCommittee.cshtml @@ -1,35 +1,34 @@ -@model PC2.Models.SteeringCommittee +@model PC2.Models.SteeringCommittee @{ ViewData["Title"] = "Edit Steering Committee Member"; }

    Edit @Model.Name

    -
    -
    +
    -
    +
    + Cancel
    - - +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/PC2/Views/People/Index.cshtml b/PC2/Views/People/Index.cshtml deleted file mode 100644 index 5fe250f2..00000000 --- a/PC2/Views/People/Index.cshtml +++ /dev/null @@ -1,41 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "People Management"; -} - -

    @ViewData["Title"]

    - -

    - Create New Person -

    - -
    - - - - - - - - - - - - @foreach (var item in Model) - { - - - - - - - - } - -
    @Html.DisplayNameFor(model => model.Name)@Html.DisplayNameFor(model => model.Title)@Html.DisplayNameFor(model => model.PriorityOrder)TypeActions
    @Html.DisplayFor(modelItem => item.Name)@Html.DisplayFor(modelItem => item.Title)@Html.DisplayFor(modelItem => item.PriorityOrder)@item.GetType().Name - Edit - Details - Delete -
    -
    \ No newline at end of file diff --git a/PC2/Views/People/IndexBoard.cshtml b/PC2/Views/People/IndexBoard.cshtml new file mode 100644 index 00000000..fd4ae6cb --- /dev/null +++ b/PC2/Views/People/IndexBoard.cshtml @@ -0,0 +1,35 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Board Members"; +} + +

    @ViewData["Title"]

    + +

    + Create New Board Member +

    + + + + + + + + + + + @foreach (var item in Model) + { + + + + + + + } + +
    @Html.DisplayNameFor(model => model.Name)@Html.DisplayNameFor(model => model.Title)Start of Membership
    @Html.DisplayFor(modelItem => item.Name)@Html.DisplayFor(modelItem => item.Title)Member since @Html.DisplayFor(modelItem => item.MembershipStart) + Edit | + Delete +
    diff --git a/PC2/Views/People/IndexStaff.cshtml b/PC2/Views/People/IndexStaff.cshtml new file mode 100644 index 00000000..b3c33b99 --- /dev/null +++ b/PC2/Views/People/IndexStaff.cshtml @@ -0,0 +1,39 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Staff Members"; +} + +

    @ViewData["Title"]

    + +

    + Create New Staff Member +

    + + + + + + + + + + + + + @foreach (var item in Model) + { + + + + + + + + + } + +
    @Html.DisplayNameFor(model => model.Name)@Html.DisplayNameFor(model => model.Title)@Html.DisplayNameFor(model => model.Phone)@Html.DisplayNameFor(model => model.Extension)@Html.DisplayNameFor(model => model.Email)
    @Html.DisplayFor(modelItem => item.Name)@Html.DisplayFor(modelItem => item.Title)@Html.DisplayFor(modelItem => item.Phone)@Html.DisplayFor(modelItem => item.Extension)@Html.DisplayFor(modelItem => item.Email) + Edit | + Delete +
    diff --git a/PC2/Views/People/IndexSteeringCommittee.cshtml b/PC2/Views/People/IndexSteeringCommittee.cshtml new file mode 100644 index 00000000..9178f5d6 --- /dev/null +++ b/PC2/Views/People/IndexSteeringCommittee.cshtml @@ -0,0 +1,33 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Steering Committee Members"; +} + +

    @ViewData["Title"]

    + +

    + Create New +

    + + + + + + + + + + @foreach (var item in Model) + { + + + + + + } + +
    @Html.DisplayNameFor(model => model.Name)@Html.DisplayNameFor(model => model.Title)
    @Html.DisplayFor(modelItem => item.Name)@Html.DisplayFor(modelItem => item.Title) + Edit | + Delete +
    diff --git a/PC2/Views/Shared/_Layout.cshtml b/PC2/Views/Shared/_Layout.cshtml index 377364c2..3e3cf1f9 100644 --- a/PC2/Views/Shared/_Layout.cshtml +++ b/PC2/Views/Shared/_Layout.cshtml @@ -102,13 +102,12 @@ Members From 956d8d816d7e4cfe16ef3c2402a3bad0a2914502 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Mon, 23 Feb 2026 07:53:26 -0800 Subject: [PATCH 05/19] Move ImageUrl property from Staff to People base class ImageUrl is now defined in the People base class, allowing all People entities to have an associated image URL. The property retains its DataType attribute for URL validation. --- PC2/Models/People.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PC2/Models/People.cs b/PC2/Models/People.cs index 4f4e507c..d2c5753f 100644 --- a/PC2/Models/People.cs +++ b/PC2/Models/People.cs @@ -25,6 +25,12 @@ public class People /// is a higher priority ///
    public byte PriorityOrder { get; set; } + + /// + /// URL to the person's photo/image. + /// + [DataType(DataType.Url)] + public string? ImageUrl { get; set; } } /// @@ -50,12 +56,6 @@ public class Staff : People [EmailAddress] public required string Email { get; set; } - /// - /// URL to the staff member's photo/image. - /// - [DataType(DataType.Url)] - public string? ImageUrl { get; set; } - /// /// Gets a formatted display of the phone number and extension. /// From 01b7bd95ea6be52dee34b68c2dfa01c0407da86c Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Mon, 23 Feb 2026 08:23:21 -0800 Subject: [PATCH 06/19] Unify People CRUD for Staff, Board, and Committee Refactored the management of Staff, Board, and Steering Committee members into a single, generic CRUD interface using a new PersonType enum and PersonViewModel. Replaced all separate controller actions and views with unified, type-driven implementations. Updated navigation and removed old, redundant ViewModels. This reduces duplication, simplifies maintenance, and provides a consistent UI for all people management. --- PC2/Controllers/PeopleController.cs | 355 ++++++++---------- PC2/Models/People.cs | 7 + PC2/Models/ViewModels/PersonViewModel.cs | 84 +++++ PC2/Models/ViewModels/StaffViewModels.cs | 116 ------ PC2/Views/People/Create.cshtml | 119 ++++++ PC2/Views/People/CreateBoard.cshtml | 47 --- PC2/Views/People/CreateStaff.cshtml | 84 ----- .../People/CreateSteeringCommittee.cshtml | 33 -- PC2/Views/People/Delete.cshtml | 46 +++ PC2/Views/People/DeleteBoard.cshtml | 26 -- PC2/Views/People/DeleteStaff.cshtml | 30 -- .../People/DeleteSteeringCommittee.cshtml | 24 -- PC2/Views/People/Edit.cshtml | 147 ++++++++ PC2/Views/People/EditBoard.cshtml | 39 -- PC2/Views/People/EditStaff.cshtml | 112 ------ PC2/Views/People/EditSteeringCommittee.cshtml | 34 -- PC2/Views/People/Index.cshtml | 62 +++ PC2/Views/People/IndexBoard.cshtml | 35 -- PC2/Views/People/IndexStaff.cshtml | 39 -- .../People/IndexSteeringCommittee.cshtml | 33 -- PC2/Views/Shared/_Layout.cshtml | 12 +- 21 files changed, 626 insertions(+), 858 deletions(-) create mode 100644 PC2/Models/ViewModels/PersonViewModel.cs delete mode 100644 PC2/Models/ViewModels/StaffViewModels.cs create mode 100644 PC2/Views/People/Create.cshtml delete mode 100644 PC2/Views/People/CreateBoard.cshtml delete mode 100644 PC2/Views/People/CreateStaff.cshtml delete mode 100644 PC2/Views/People/CreateSteeringCommittee.cshtml create mode 100644 PC2/Views/People/Delete.cshtml delete mode 100644 PC2/Views/People/DeleteBoard.cshtml delete mode 100644 PC2/Views/People/DeleteStaff.cshtml delete mode 100644 PC2/Views/People/DeleteSteeringCommittee.cshtml create mode 100644 PC2/Views/People/Edit.cshtml delete mode 100644 PC2/Views/People/EditBoard.cshtml delete mode 100644 PC2/Views/People/EditStaff.cshtml delete mode 100644 PC2/Views/People/EditSteeringCommittee.cshtml create mode 100644 PC2/Views/People/Index.cshtml delete mode 100644 PC2/Views/People/IndexBoard.cshtml delete mode 100644 PC2/Views/People/IndexStaff.cshtml delete mode 100644 PC2/Views/People/IndexSteeringCommittee.cshtml diff --git a/PC2/Controllers/PeopleController.cs b/PC2/Controllers/PeopleController.cs index e866cf08..faa0bfa6 100644 --- a/PC2/Controllers/PeopleController.cs +++ b/PC2/Controllers/PeopleController.cs @@ -21,261 +21,218 @@ public PeopleController(ApplicationDbContext context, AzureBlobUploader azureBlo _imageService = imageService; } - // ---- Staff ---- - - public async Task IndexStaff() + public async Task Index(PersonType type) { - return View(await StaffDB.GetAllStaffForEditing(_context)); + ViewData["PersonType"] = type; + IEnumerable people = type switch + { + PersonType.Staff => (await StaffDB.GetAllStaffForEditing(_context)).Select(PersonViewModel.FromStaff), + PersonType.Board => (await BoardDB.GetAllBoardMembersForEditing(_context)).Select(PersonViewModel.FromBoard), + PersonType.SteeringCommittee => (await SteeringCommitteeDB.GetAllSteeringCommittee(_context)).Select(PersonViewModel.FromSteeringCommittee), + _ => [] + }; + return View(people); } [HttpGet] - public IActionResult CreateStaff() + public IActionResult Create(PersonType type) { - return View(new CreateStaffViewModel()); + return View(new PersonViewModel { Type = type }); } [HttpPost] - public async Task CreateStaff(CreateStaffViewModel model) + public async Task Create(PersonViewModel model) { + ValidateTypeSpecificFields(model, isCreate: true); + if (ModelState.IsValid) { - var staff = new Staff + switch (model.Type) { - Name = model.Name, - Title = model.Title, - Phone = model.Phone, - Extension = model.Extension, - Email = model.Email, - PriorityOrder = model.PriorityOrder - }; - - await HandlePhotoUpload(model.PhotoFile, staff); - await StaffDB.AddStaff(_context, staff); - return RedirectToAction(nameof(IndexStaff)); + case PersonType.Staff: + var staff = new Staff + { + Name = model.Name, + Title = model.Title, + Phone = model.Phone, + Extension = model.Extension, + Email = model.Email!, + PriorityOrder = model.PriorityOrder + }; + await HandlePhotoUpload(model.PhotoFile, staff); + await StaffDB.AddStaff(_context, staff); + break; + + case PersonType.Board: + var board = new Board + { + Name = model.Name, + Title = model.Title, + MembershipStart = model.MembershipStart!, + PriorityOrder = model.PriorityOrder + }; + await HandlePhotoUpload(model.PhotoFile, board); + await BoardDB.CreateBoardMember(_context, board); + break; + + case PersonType.SteeringCommittee: + var sc = new SteeringCommittee + { + Name = model.Name, + Title = model.Title, + PriorityOrder = model.PriorityOrder + }; + await HandlePhotoUpload(model.PhotoFile, sc); + await SteeringCommitteeDB.Create(_context, sc); + break; + } + return RedirectToAction(nameof(Index), new { type = model.Type }); } return View(model); } [HttpGet] - public async Task EditStaff(int id) + public async Task Edit(int id, PersonType type) { - var staff = await StaffDB.GetStaffMember(_context, id); - if (staff == null) + PersonViewModel? model = type switch { - return NotFound(); - } - - var model = new EditStaffViewModel - { - ID = staff.ID, - Name = staff.Name, - Title = staff.Title, - Phone = staff.Phone, - Extension = staff.Extension, - Email = staff.Email, - CurrentImageUrl = staff.ImageUrl, - PriorityOrder = staff.PriorityOrder + PersonType.Staff => await StaffDB.GetStaffMember(_context, id) is Staff s + ? PersonViewModel.FromStaff(s) : null, + PersonType.Board => await BoardDB.GetBoardMember(_context, id) is Board b + ? PersonViewModel.FromBoard(b) : null, + PersonType.SteeringCommittee => await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id) is SteeringCommittee sc + ? PersonViewModel.FromSteeringCommittee(sc) : null, + _ => null }; + if (model == null) return NotFound(); return View(model); } [HttpPost] - public async Task EditStaff(EditStaffViewModel model) + public async Task Edit(PersonViewModel model) { + ValidateTypeSpecificFields(model, isCreate: false); + if (ModelState.IsValid) { - var staff = await StaffDB.GetStaffMember(_context, model.ID); - if (staff == null) - { - return NotFound(); - } - - staff.Name = model.Name; - staff.Title = model.Title; - staff.Phone = model.Phone; - staff.Extension = model.Extension; - staff.Email = model.Email; - staff.PriorityOrder = model.PriorityOrder; - - if (model.RemovePhoto) - { - await RemoveStaffPhoto(staff); - } - else if (model.PhotoFile != null) + switch (model.Type) { - await HandlePhotoUpload(model.PhotoFile, staff, model.ID); + case PersonType.Staff: + var staff = await StaffDB.GetStaffMember(_context, model.ID); + if (staff == null) return NotFound(); + staff.Name = model.Name; + staff.Title = model.Title; + staff.Phone = model.Phone; + staff.Extension = model.Extension; + staff.Email = model.Email!; + staff.PriorityOrder = model.PriorityOrder; + if (model.RemovePhoto) await RemovePersonPhoto(staff); + else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, staff, model.ID); + await StaffDB.SaveChanges(_context, staff); + break; + + case PersonType.Board: + var board = await BoardDB.GetBoardMember(_context, model.ID); + if (board == null) return NotFound(); + board.Name = model.Name; + board.Title = model.Title; + board.MembershipStart = model.MembershipStart!; + board.PriorityOrder = model.PriorityOrder; + if (model.RemovePhoto) await RemovePersonPhoto(board); + else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, board, model.ID); + await BoardDB.EditBoardMember(_context, board); + break; + + case PersonType.SteeringCommittee: + var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, model.ID); + if (sc == null) return NotFound(); + sc.Name = model.Name; + sc.Title = model.Title; + sc.PriorityOrder = model.PriorityOrder; + if (model.RemovePhoto) await RemovePersonPhoto(sc); + else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, sc, model.ID); + await SteeringCommitteeDB.EditSteeringCommittee(_context, sc); + break; } - - await StaffDB.SaveChanges(_context, staff); - return RedirectToAction(nameof(IndexStaff)); + return RedirectToAction(nameof(Index), new { type = model.Type }); } - - model.CurrentImageUrl = (await StaffDB.GetStaffMember(_context, model.ID))?.ImageUrl; return View(model); } [HttpGet] - public async Task DeleteStaff(int id) + public async Task Delete(int id, PersonType type) { - return View(await StaffDB.GetStaffMember(_context, id)); - } - - [HttpPost, ActionName("DeleteStaff")] - public async Task ConfirmDeleteStaff(int id) - { - Staff? staff = await StaffDB.GetStaffMember(_context, id); - if (staff == null) + PersonViewModel? model = type switch { - return NotFound(); - } - - await StaffDB.Delete(_context, staff); - return RedirectToAction(nameof(IndexStaff)); - } - - // ---- Board ---- - - public async Task IndexBoard() - { - return View(await BoardDB.GetAllBoardMembersForEditing(_context)); - } - - [HttpGet] - public IActionResult CreateBoard() - { - return View(); - } - - [HttpPost] - public async Task CreateBoard(Board board) - { - if (ModelState.IsValid) - { - await BoardDB.CreateBoardMember(_context, board); - return RedirectToAction(nameof(IndexBoard)); - } - return View(board); - } - - [HttpGet] - public async Task EditBoard(int id) - { - return View(await BoardDB.GetBoardMember(_context, id)); - } - - [HttpPost] - public async Task EditBoard(Board board) - { - if (ModelState.IsValid) - { - await BoardDB.EditBoardMember(_context, board); - return RedirectToAction(nameof(IndexBoard)); - } - return View(board); - } - - [HttpGet] - public async Task DeleteBoard(int id) - { - return View(await BoardDB.GetBoardMember(_context, id)); - } - - [HttpPost, ActionName("DeleteBoard")] - public async Task ConfirmDeleteBoard(int id) - { - Board? board = await BoardDB.GetBoardMember(_context, id); - if (board == null) - { - return NotFound(); - } - - await BoardDB.Delete(_context, board); - return RedirectToAction(nameof(IndexBoard)); - } - - // ---- Steering Committee ---- - - public async Task IndexSteeringCommittee() - { - return View(await SteeringCommitteeDB.GetAllSteeringCommittee(_context)); - } - - [HttpGet] - public IActionResult CreateSteeringCommittee() - { - return View(); - } - - [HttpPost] - public async Task CreateSteeringCommittee(SteeringCommittee steeringCommittee) - { - if (ModelState.IsValid) - { - await SteeringCommitteeDB.Create(_context, steeringCommittee); - return RedirectToAction(nameof(IndexSteeringCommittee)); - } - return View(steeringCommittee); - } + PersonType.Staff => await StaffDB.GetStaffMember(_context, id) is Staff s + ? PersonViewModel.FromStaff(s) : null, + PersonType.Board => await BoardDB.GetBoardMember(_context, id) is Board b + ? PersonViewModel.FromBoard(b) : null, + PersonType.SteeringCommittee => await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id) is SteeringCommittee sc + ? PersonViewModel.FromSteeringCommittee(sc) : null, + _ => null + }; - [HttpGet] - public async Task EditSteeringCommittee(int id) - { - return View(await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id)); + if (model == null) return NotFound(); + return View(model); } - [HttpPost] - public async Task EditSteeringCommittee(SteeringCommittee steeringCommittee) + [HttpPost, ActionName("Delete")] + public async Task ConfirmDelete(int id, PersonType type) { - if (ModelState.IsValid) + switch (type) { - await SteeringCommitteeDB.EditSteeringCommittee(_context, steeringCommittee); - return RedirectToAction(nameof(IndexSteeringCommittee)); + case PersonType.Staff: + var staff = await StaffDB.GetStaffMember(_context, id); + if (staff == null) return NotFound(); + await StaffDB.Delete(_context, staff); + break; + + case PersonType.Board: + var board = await BoardDB.GetBoardMember(_context, id); + if (board == null) return NotFound(); + await BoardDB.Delete(_context, board); + break; + + case PersonType.SteeringCommittee: + var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id); + if (sc == null) return NotFound(); + await SteeringCommitteeDB.Delete(_context, sc); + break; } - return View(steeringCommittee); + return RedirectToAction(nameof(Index), new { type }); } - [HttpGet] - public async Task DeleteSteeringCommittee(int id) + private void ValidateTypeSpecificFields(PersonViewModel model, bool isCreate) { - return View(await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id)); - } - - [HttpPost, ActionName("DeleteSteeringCommittee")] - public async Task ConfirmDeleteSteeringCommittee(int id) - { - SteeringCommittee? steeringCommittee = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id); - if (steeringCommittee == null) + if (model.Type == PersonType.Staff) { - return NotFound(); + if (string.IsNullOrWhiteSpace(model.Email)) + ModelState.AddModelError(nameof(PersonViewModel.Email), "Email is required for staff members."); + if (isCreate && model.PhotoFile == null) + ModelState.AddModelError(nameof(PersonViewModel.PhotoFile), "Please upload a photo."); } - - await SteeringCommitteeDB.Delete(_context, steeringCommittee); - return RedirectToAction(nameof(IndexSteeringCommittee)); + if (model.Type == PersonType.Board && string.IsNullOrWhiteSpace(model.MembershipStart)) + ModelState.AddModelError(nameof(PersonViewModel.MembershipStart), "Membership start year is required."); } - // ---- Photo helpers ---- - - private async Task HandlePhotoUpload(IFormFile? photoFile, Staff staff, int? staffId = null) + private async Task HandlePhotoUpload(IFormFile? photoFile, People person, int? personId = null) { if (photoFile == null || photoFile.Length == 0) return; try { if (!ImageService.IsValidImageFile(photoFile)) - { throw new InvalidOperationException("Please upload a valid image file (JPEG, PNG, GIF, or BMP)."); - } - if (staffId.HasValue && !string.IsNullOrEmpty(staff.ImageUrl)) - { - await RemoveStaffPhoto(staff); - } + if (personId.HasValue && !string.IsNullOrEmpty(person.ImageUrl)) + await RemovePersonPhoto(person); - var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, staffId ?? 0); + var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, personId ?? 0); using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream()); var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); - staff.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); + person.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); } catch (Exception ex) { @@ -283,24 +240,22 @@ private async Task HandlePhotoUpload(IFormFile? photoFile, Staff staff, int? sta } } - private async Task RemoveStaffPhoto(Staff staff) + private async Task RemovePersonPhoto(People person) { - if (string.IsNullOrEmpty(staff.ImageUrl)) return; + if (string.IsNullOrEmpty(person.ImageUrl)) return; try { - var fileName = staff.ImageUrl.Split('/').LastOrDefault(); + var fileName = person.ImageUrl.Split('/').LastOrDefault(); if (!string.IsNullOrEmpty(fileName)) - { await _azureBlobUploader.DeleteFileAsync(fileName); - } } catch (Exception ex) { Console.WriteLine($"Error deleting photo: {ex.Message}"); } - staff.ImageUrl = null; + person.ImageUrl = null; } } } diff --git a/PC2/Models/People.cs b/PC2/Models/People.cs index d2c5753f..afd813d3 100644 --- a/PC2/Models/People.cs +++ b/PC2/Models/People.cs @@ -97,4 +97,11 @@ public class Board : People /// public string MembershipStart { get; set; } } + + public enum PersonType + { + Staff, + Board, + SteeringCommittee + } } diff --git a/PC2/Models/ViewModels/PersonViewModel.cs b/PC2/Models/ViewModels/PersonViewModel.cs new file mode 100644 index 00000000..1f504add --- /dev/null +++ b/PC2/Models/ViewModels/PersonViewModel.cs @@ -0,0 +1,84 @@ +using System.ComponentModel.DataAnnotations; + +namespace PC2.Models.ViewModels +{ + public class PersonViewModel + { + public int ID { get; set; } + + [Required] + public PersonType Type { get; set; } + + // ---- Common (People base) ---- + + [Required] + public string Name { get; set; } = string.Empty; + + public string? Title { get; set; } + + [Required] + [Display(Name = "Sort Priority")] + public byte PriorityOrder { get; set; } = 10; + + // ---- Photo ---- + + public string? CurrentImageUrl { get; set; } + + [Display(Name = "Photo")] + public IFormFile? PhotoFile { get; set; } + + [Display(Name = "Remove current photo")] + public bool RemovePhoto { get; set; } + + // ---- Staff-specific ---- + + public string? Phone { get; set; } + + public int? Extension { get; set; } + + [DataType(DataType.EmailAddress)] + [EmailAddress] + public string? Email { get; set; } + + // ---- Board-specific ---- + + [Display(Name = "Membership Start Year")] + public string? MembershipStart { get; set; } + + // ---- Factory methods ---- + + public static PersonViewModel FromStaff(Staff s) => new() + { + ID = s.ID, + Type = PersonType.Staff, + Name = s.Name, + Title = s.Title, + PriorityOrder = s.PriorityOrder, + CurrentImageUrl = s.ImageUrl, + Phone = s.Phone, + Extension = s.Extension, + Email = s.Email + }; + + public static PersonViewModel FromBoard(Board b) => new() + { + ID = b.ID, + Type = PersonType.Board, + Name = b.Name, + Title = b.Title, + PriorityOrder = b.PriorityOrder, + CurrentImageUrl = b.ImageUrl, + MembershipStart = b.MembershipStart + }; + + public static PersonViewModel FromSteeringCommittee(SteeringCommittee sc) => new() + { + ID = sc.ID, + Type = PersonType.SteeringCommittee, + Name = sc.Name, + Title = sc.Title, + PriorityOrder = sc.PriorityOrder, + CurrentImageUrl = sc.ImageUrl + }; + } +} diff --git a/PC2/Models/ViewModels/StaffViewModels.cs b/PC2/Models/ViewModels/StaffViewModels.cs deleted file mode 100644 index d0e9f954..00000000 --- a/PC2/Models/ViewModels/StaffViewModels.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace PC2.Models.ViewModels -{ - /// - /// ViewModel for creating a new staff member with file upload support - /// - public class CreateStaffViewModel - { - /// - /// The person's full name - /// - [Required] - public string Name { get; set; } = string.Empty; - - /// - /// Position title - /// - public string? Title { get; set; } - - /// - /// The staff/person's phone number. - /// - public string? Phone { get; set; } - - /// - /// The phone extension. - /// - public int? Extension { get; set; } - - /// - /// The staff/person's email address. - /// - [Required] - [DataType(DataType.EmailAddress)] - [EmailAddress] - public string Email { get; set; } = string.Empty; - - /// - /// Photo file upload - /// - [Required(ErrorMessage = "Please upload a photo.")] - [Display(Name = "Photo")] - public IFormFile? PhotoFile { get; set; } - - /// - /// Used to sort People by their display priority. Lower number - /// is a higher priority - /// - [Required] - [Display(Name = "Sort Priority")] - public byte PriorityOrder { get; set; } = 10; - } - - /// - /// ViewModel for editing an existing staff member with file upload support - /// - public class EditStaffViewModel - { - public int ID { get; set; } - - /// - /// The person's full name - /// - [Required] - public string Name { get; set; } = string.Empty; - - /// - /// Position title - /// - public string? Title { get; set; } - - /// - /// The staff/person's phone number. - /// - public string? Phone { get; set; } - - /// - /// The phone extension. - /// - public int? Extension { get; set; } - - /// - /// The staff/person's email address. - /// - [Required] - [DataType(DataType.EmailAddress)] - [EmailAddress] - public string Email { get; set; } = string.Empty; - - /// - /// Current photo URL - /// - public string? CurrentImageUrl { get; set; } - - /// - /// New photo file upload - /// - [Display(Name = "New Photo")] - public IFormFile? PhotoFile { get; set; } - - /// - /// Whether to remove the current photo - /// - [Display(Name = "Remove current photo")] - public bool RemovePhoto { get; set; } - - /// - /// Used to sort People by their display priority. Lower number - /// is a higher priority - /// - [Required] - [Display(Name = "Sort Priority")] - public byte PriorityOrder { get; set; } = 10; - } -} \ No newline at end of file diff --git a/PC2/Views/People/Create.cshtml b/PC2/Views/People/Create.cshtml new file mode 100644 index 00000000..791be808 --- /dev/null +++ b/PC2/Views/People/Create.cshtml @@ -0,0 +1,119 @@ +@model PC2.Models.ViewModels.PersonViewModel +@using PC2.Models + +@{ + ViewData["Title"] = Model.Type switch + { + PersonType.Staff => "Create Staff Member", + PersonType.Board => "Create Board Member", + PersonType.SteeringCommittee => "Create Steering Committee Member", + _ => "Create Member" + }; +} + +

    @ViewData["Title"]

    +
    +
    +
    +
    +
    + + +
    + + + +
    + +
    + + + +
    + + @if (Model.Type == PersonType.Staff) + { +
    +
    +
    + + + +
    +
    +
    +
    + + + +
    +
    +
    +
    + + + +
    + } + + @if (Model.Type == PersonType.Board) + { +
    + + + +
    + } + +
    +
    +
    Photo
    +
    +
    +
    + + + + + Upload a photo file (JPEG, PNG, GIF, or BMP). Large images will be automatically resized. + @if (Model.Type == PersonType.Staff) { Required for staff. } + +
    +
    +
    + +
    + + @if (Model.Type == PersonType.Staff) + { + + } + else if (Model.Type == PersonType.Board) + { + + } + else + { + + } + +
    + +
    + + Cancel +
    +
    +
    +
    + +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/PC2/Views/People/CreateBoard.cshtml b/PC2/Views/People/CreateBoard.cshtml deleted file mode 100644 index e72a8da5..00000000 --- a/PC2/Views/People/CreateBoard.cshtml +++ /dev/null @@ -1,47 +0,0 @@ -@model PC2.Models.Board - -@{ - ViewData["Title"] = "Create Board Member"; -} - -

    @ViewData["Title"]

    -
    -
    -
    -
    -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - Cancel -
    -
    -
    -
    - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} -} diff --git a/PC2/Views/People/CreateStaff.cshtml b/PC2/Views/People/CreateStaff.cshtml deleted file mode 100644 index e97f36bc..00000000 --- a/PC2/Views/People/CreateStaff.cshtml +++ /dev/null @@ -1,84 +0,0 @@ -@model PC2.Models.ViewModels.CreateStaffViewModel - -@{ - ViewData["Title"] = "Create Staff Member"; -} - -

    @ViewData["Title"]

    -
    -
    -
    -
    -
    - -
    - - - -
    - -
    - - - -
    - -
    -
    -
    - - - -
    -
    -
    -
    - - - -
    -
    -
    - -
    - - - -
    - -
    -
    -
    Photo
    -
    -
    -
    - - - - - Upload a photo file (JPEG, PNG, GIF, or BMP). Large images will be automatically resized. - -
    -
    -
    - -
    - - - -
    - -
    - - Cancel -
    -
    -
    -
    - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} -} diff --git a/PC2/Views/People/CreateSteeringCommittee.cshtml b/PC2/Views/People/CreateSteeringCommittee.cshtml deleted file mode 100644 index 22299b61..00000000 --- a/PC2/Views/People/CreateSteeringCommittee.cshtml +++ /dev/null @@ -1,33 +0,0 @@ -@model PC2.Models.SteeringCommittee - -@{ - ViewData["Title"] = "Create Steering Committee Member"; -} - -

    @ViewData["Title"]

    -
    -
    -
    -
    -
    -
    - - - -
    -
    - - - -
    -
    - - Cancel -
    -
    -
    -
    - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} -} diff --git a/PC2/Views/People/Delete.cshtml b/PC2/Views/People/Delete.cshtml new file mode 100644 index 00000000..7862104f --- /dev/null +++ b/PC2/Views/People/Delete.cshtml @@ -0,0 +1,46 @@ +@model PC2.Models.ViewModels.PersonViewModel +@using PC2.Models + +@{ + ViewData["Title"] = Model.Type switch + { + PersonType.Staff => "Delete Staff Member", + PersonType.Board => "Delete Board Member", + PersonType.SteeringCommittee => "Delete Steering Committee Member", + _ => "Delete Member" + }; +} + +

    Delete @Model.Name

    + +

    Are you sure you want to delete @Model.Name?

    +
    +
    +
    +
    Name
    +
    @Model.Name
    +
    Title
    +
    @Model.Title
    + + @if (Model.Type == PersonType.Staff) + { +
    Phone
    +
    @Model.Phone
    +
    Email
    +
    @Model.Email
    + } + + @if (Model.Type == PersonType.Board) + { +
    Member Since
    +
    @Model.MembershipStart
    + } +
    + +
    + + + | + Back to List +
    +
    diff --git a/PC2/Views/People/DeleteBoard.cshtml b/PC2/Views/People/DeleteBoard.cshtml deleted file mode 100644 index 4e45cf1c..00000000 --- a/PC2/Views/People/DeleteBoard.cshtml +++ /dev/null @@ -1,26 +0,0 @@ -@model PC2.Models.Board - -@{ - ViewData["Title"] = "Delete Board Member"; -} - -

    Delete @Model.Name

    - -

    Are you sure you want to delete @Model.Name?

    -
    -
    -
    -
    @Html.DisplayNameFor(model => model.Name)
    -
    @Html.DisplayFor(model => model.Name)
    -
    @Html.DisplayNameFor(model => model.Title)
    -
    @Html.DisplayFor(model => model.Title)
    -
    @Html.DisplayNameFor(model => model.MembershipStart)
    -
    @Html.DisplayFor(model => model.MembershipStart)
    -
    - -
    - - | - Back to List -
    -
    diff --git a/PC2/Views/People/DeleteStaff.cshtml b/PC2/Views/People/DeleteStaff.cshtml deleted file mode 100644 index 4f687d98..00000000 --- a/PC2/Views/People/DeleteStaff.cshtml +++ /dev/null @@ -1,30 +0,0 @@ -@model PC2.Models.Staff - -@{ - ViewData["Title"] = "Delete Staff Member"; -} - -

    Delete @Model.Name

    - -

    Are you sure you want to delete @Model.Name?

    -
    -
    -
    -
    @Html.DisplayNameFor(model => model.Name)
    -
    @Html.DisplayFor(model => model.Name)
    -
    @Html.DisplayNameFor(model => model.Title)
    -
    @Html.DisplayFor(model => model.Title)
    -
    @Html.DisplayNameFor(model => model.Phone)
    -
    @Html.DisplayFor(model => model.Phone)
    -
    @Html.DisplayNameFor(model => model.Extension)
    -
    @Html.DisplayFor(model => model.Extension)
    -
    @Html.DisplayNameFor(model => model.Email)
    -
    @Html.DisplayFor(model => model.Email)
    -
    - -
    - - | - Back to List -
    -
    diff --git a/PC2/Views/People/DeleteSteeringCommittee.cshtml b/PC2/Views/People/DeleteSteeringCommittee.cshtml deleted file mode 100644 index e9242c6d..00000000 --- a/PC2/Views/People/DeleteSteeringCommittee.cshtml +++ /dev/null @@ -1,24 +0,0 @@ -@model PC2.Models.SteeringCommittee - -@{ - ViewData["Title"] = "Delete Steering Committee Member"; -} - -

    Delete @Model.Name

    - -

    Are you sure you want to delete @Model.Name?

    -
    -
    -
    -
    @Html.DisplayNameFor(model => model.Name)
    -
    @Html.DisplayFor(model => model.Name)
    -
    @Html.DisplayNameFor(model => model.Title)
    -
    @Html.DisplayFor(model => model.Title)
    -
    - -
    - - | - Back to List -
    -
    diff --git a/PC2/Views/People/Edit.cshtml b/PC2/Views/People/Edit.cshtml new file mode 100644 index 00000000..dc940aca --- /dev/null +++ b/PC2/Views/People/Edit.cshtml @@ -0,0 +1,147 @@ +@model PC2.Models.ViewModels.PersonViewModel +@using PC2.Models + +@{ + ViewData["Title"] = Model.Type switch + { + PersonType.Staff => "Edit Staff Member", + PersonType.Board => "Edit Board Member", + PersonType.SteeringCommittee => "Edit Steering Committee Member", + _ => "Edit Member" + }; +} + +

    Edit @Model.Name

    +
    +
    +
    +
    +
    + + + + +
    + + + +
    + +
    + + + +
    + + @if (Model.Type == PersonType.Staff) + { +
    +
    +
    + + + +
    +
    +
    +
    + + + +
    +
    +
    +
    + + + +
    + } + + @if (Model.Type == PersonType.Board) + { +
    + + + +
    + } + +
    +
    +
    Photo
    +
    +
    + @if (!string.IsNullOrEmpty(Model.CurrentImageUrl)) + { +
    + +
    + @Model.Name +
    +
    +
    + + +
    + } +
    + + + + + Upload a new photo (JPEG, PNG, GIF, or BMP). Large images will be automatically resized. + +
    +
    +
    + +
    + + @if (Model.Type == PersonType.Staff) + { + + } + else if (Model.Type == PersonType.Board) + { + + } + else + { + + } + +
    + +
    + + Cancel +
    +
    +
    +
    + +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} + +} diff --git a/PC2/Views/People/EditBoard.cshtml b/PC2/Views/People/EditBoard.cshtml deleted file mode 100644 index 92bb335f..00000000 --- a/PC2/Views/People/EditBoard.cshtml +++ /dev/null @@ -1,39 +0,0 @@ -@model PC2.Models.Board - -@{ - ViewData["Title"] = "Edit Board Member"; -} - -

    Edit @Model.Name

    -
    -
    -
    -
    -
    - -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    - - Cancel -
    -
    -
    -
    - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} -} diff --git a/PC2/Views/People/EditStaff.cshtml b/PC2/Views/People/EditStaff.cshtml deleted file mode 100644 index 420ca7c5..00000000 --- a/PC2/Views/People/EditStaff.cshtml +++ /dev/null @@ -1,112 +0,0 @@ -@model PC2.Models.ViewModels.EditStaffViewModel - -@{ - ViewData["Title"] = "Edit Staff Member"; -} - -

    Edit @Model.Name

    -
    -
    -
    -
    -
    - - -
    - - - -
    - -
    - - - -
    - -
    -
    -
    - - - -
    -
    -
    -
    - - - -
    -
    -
    - -
    - - - -
    - -
    -
    -
    Photo
    -
    -
    - @if (!string.IsNullOrEmpty(Model.CurrentImageUrl)) - { -
    - -
    - @Model.Name -
    -
    - -
    - - -
    - } - -
    - - - - - Upload a new photo file (JPEG, PNG, GIF, or BMP). Large images will be automatically resized. - -
    -
    -
    - -
    - - - -
    - -
    - - Cancel -
    -
    -
    -
    - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} - -} diff --git a/PC2/Views/People/EditSteeringCommittee.cshtml b/PC2/Views/People/EditSteeringCommittee.cshtml deleted file mode 100644 index 6e31e4f7..00000000 --- a/PC2/Views/People/EditSteeringCommittee.cshtml +++ /dev/null @@ -1,34 +0,0 @@ -@model PC2.Models.SteeringCommittee - -@{ - ViewData["Title"] = "Edit Steering Committee Member"; -} - -

    Edit @Model.Name

    -
    -
    -
    -
    -
    - -
    - - - -
    -
    - - - -
    -
    - - Cancel -
    -
    -
    -
    - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} -} diff --git a/PC2/Views/People/Index.cshtml b/PC2/Views/People/Index.cshtml new file mode 100644 index 00000000..cb146f2d --- /dev/null +++ b/PC2/Views/People/Index.cshtml @@ -0,0 +1,62 @@ +@model IEnumerable +@using PC2.Models + +@{ + var type = (PersonType)ViewData["PersonType"]!; + ViewData["Title"] = type switch + { + PersonType.Staff => "Staff Members", + PersonType.Board => "Board Members", + PersonType.SteeringCommittee => "Steering Committee Members", + _ => "Members" + }; +} + +

    @ViewData["Title"]

    + +

    + Create New +

    + + + + + + + @if (type == PersonType.Staff) + { + + + + } + @if (type == PersonType.Board) + { + + } + + + + + @foreach (var item in Model) + { + + + + @if (type == PersonType.Staff) + { + + + + } + @if (type == PersonType.Board) + { + + } + + + } + +
    NameTitlePhoneExtensionEmailMembership Start
    @item.Name@item.Title@item.Phone@item.Extension@item.EmailMember since @item.MembershipStart + Edit | + Delete +
    diff --git a/PC2/Views/People/IndexBoard.cshtml b/PC2/Views/People/IndexBoard.cshtml deleted file mode 100644 index fd4ae6cb..00000000 --- a/PC2/Views/People/IndexBoard.cshtml +++ /dev/null @@ -1,35 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Board Members"; -} - -

    @ViewData["Title"]

    - -

    - Create New Board Member -

    - - - - - - - - - - - @foreach (var item in Model) - { - - - - - - - } - -
    @Html.DisplayNameFor(model => model.Name)@Html.DisplayNameFor(model => model.Title)Start of Membership
    @Html.DisplayFor(modelItem => item.Name)@Html.DisplayFor(modelItem => item.Title)Member since @Html.DisplayFor(modelItem => item.MembershipStart) - Edit | - Delete -
    diff --git a/PC2/Views/People/IndexStaff.cshtml b/PC2/Views/People/IndexStaff.cshtml deleted file mode 100644 index b3c33b99..00000000 --- a/PC2/Views/People/IndexStaff.cshtml +++ /dev/null @@ -1,39 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Staff Members"; -} - -

    @ViewData["Title"]

    - -

    - Create New Staff Member -

    - - - - - - - - - - - - - @foreach (var item in Model) - { - - - - - - - - - } - -
    @Html.DisplayNameFor(model => model.Name)@Html.DisplayNameFor(model => model.Title)@Html.DisplayNameFor(model => model.Phone)@Html.DisplayNameFor(model => model.Extension)@Html.DisplayNameFor(model => model.Email)
    @Html.DisplayFor(modelItem => item.Name)@Html.DisplayFor(modelItem => item.Title)@Html.DisplayFor(modelItem => item.Phone)@Html.DisplayFor(modelItem => item.Extension)@Html.DisplayFor(modelItem => item.Email) - Edit | - Delete -
    diff --git a/PC2/Views/People/IndexSteeringCommittee.cshtml b/PC2/Views/People/IndexSteeringCommittee.cshtml deleted file mode 100644 index 9178f5d6..00000000 --- a/PC2/Views/People/IndexSteeringCommittee.cshtml +++ /dev/null @@ -1,33 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Steering Committee Members"; -} - -

    @ViewData["Title"]

    - -

    - Create New -

    - - - - - - - - - - @foreach (var item in Model) - { - - - - - - } - -
    @Html.DisplayNameFor(model => model.Name)@Html.DisplayNameFor(model => model.Title)
    @Html.DisplayFor(modelItem => item.Name)@Html.DisplayFor(modelItem => item.Title) - Edit | - Delete -
    diff --git a/PC2/Views/Shared/_Layout.cshtml b/PC2/Views/Shared/_Layout.cshtml index 3e3cf1f9..dcfa0d3c 100644 --- a/PC2/Views/Shared/_Layout.cshtml +++ b/PC2/Views/Shared/_Layout.cshtml @@ -102,12 +102,12 @@ Members From f3e94f08433b5bef383e4c29d40a9e4818e872ea Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Mon, 23 Feb 2026 09:18:16 -0800 Subject: [PATCH 07/19] Relax staff photo requirement; add image preview support Relaxed staff photo upload validation in controller and views. Generalized image filename generation for all person types. Added client-side image preview to Create/Edit views. Show current image in Delete view. Updated Edit view to clear preview when removing photo. Removed "Required for staff" note from Create view. --- PC2/Controllers/PeopleController.cs | 15 +++++---------- PC2/Services/ImageService.cs | 4 ++-- PC2/Views/People/Create.cshtml | 22 ++++++++++++++++++++-- PC2/Views/People/Delete.cshtml | 9 +++++++++ PC2/Views/People/Edit.cshtml | 24 ++++++++++++++++++++++++ 5 files changed, 60 insertions(+), 14 deletions(-) diff --git a/PC2/Controllers/PeopleController.cs b/PC2/Controllers/PeopleController.cs index faa0bfa6..d8b8c818 100644 --- a/PC2/Controllers/PeopleController.cs +++ b/PC2/Controllers/PeopleController.cs @@ -43,7 +43,7 @@ public IActionResult Create(PersonType type) [HttpPost] public async Task Create(PersonViewModel model) { - ValidateTypeSpecificFields(model, isCreate: true); + ValidateTypeSpecificFields(model); if (ModelState.IsValid) { @@ -112,7 +112,7 @@ public async Task Edit(int id, PersonType type) [HttpPost] public async Task Edit(PersonViewModel model) { - ValidateTypeSpecificFields(model, isCreate: false); + ValidateTypeSpecificFields(model); if (ModelState.IsValid) { @@ -204,15 +204,10 @@ public async Task ConfirmDelete(int id, PersonType type) return RedirectToAction(nameof(Index), new { type }); } - private void ValidateTypeSpecificFields(PersonViewModel model, bool isCreate) + private void ValidateTypeSpecificFields(PersonViewModel model) { - if (model.Type == PersonType.Staff) - { - if (string.IsNullOrWhiteSpace(model.Email)) - ModelState.AddModelError(nameof(PersonViewModel.Email), "Email is required for staff members."); - if (isCreate && model.PhotoFile == null) - ModelState.AddModelError(nameof(PersonViewModel.PhotoFile), "Please upload a photo."); - } + if (model.Type == PersonType.Staff && string.IsNullOrWhiteSpace(model.Email)) + ModelState.AddModelError(nameof(PersonViewModel.Email), "Email is required for staff members."); if (model.Type == PersonType.Board && string.IsNullOrWhiteSpace(model.MembershipStart)) ModelState.AddModelError(nameof(PersonViewModel.MembershipStart), "Membership start year is required."); } diff --git a/PC2/Services/ImageService.cs b/PC2/Services/ImageService.cs index 4a7b91f6..8c99479d 100644 --- a/PC2/Services/ImageService.cs +++ b/PC2/Services/ImageService.cs @@ -118,11 +118,11 @@ public static bool IsValidImageFile(IFormFile file) /// /// Gets a safe filename for uploaded images /// - public static string GetSafeImageFileName(string originalFileName, int staffId) + public static string GetSafeImageFileName(string originalFileName, int personId) { var extension = Path.GetExtension(originalFileName); var timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); - return $"staff_{staffId}_{timestamp}{extension}"; + return $"person_{personId}_{timestamp}{extension}"; } } } \ No newline at end of file diff --git a/PC2/Views/People/Create.cshtml b/PC2/Views/People/Create.cshtml index 791be808..0f1bbb6d 100644 --- a/PC2/Views/People/Create.cshtml +++ b/PC2/Views/People/Create.cshtml @@ -72,12 +72,13 @@
    - + Upload a photo file (JPEG, PNG, GIF, or BMP). Large images will be automatically resized. - @if (Model.Type == PersonType.Staff) { Required for staff. } +
    @@ -116,4 +117,21 @@ @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} + } diff --git a/PC2/Views/People/Delete.cshtml b/PC2/Views/People/Delete.cshtml index 7862104f..c93f594f 100644 --- a/PC2/Views/People/Delete.cshtml +++ b/PC2/Views/People/Delete.cshtml @@ -16,6 +16,15 @@

    Are you sure you want to delete @Model.Name?


    + + @if (!string.IsNullOrEmpty(Model.CurrentImageUrl)) + { +
    + @Model.Name +
    + } +
    Name
    @Model.Name
    diff --git a/PC2/Views/People/Edit.cshtml b/PC2/Views/People/Edit.cshtml index dc940aca..7e59ad91 100644 --- a/PC2/Views/People/Edit.cshtml +++ b/PC2/Views/People/Edit.cshtml @@ -93,6 +93,8 @@ Upload a new photo (JPEG, PNG, GIF, or BMP). Large images will be automatically resized. +
    @@ -135,12 +137,34 @@ const removePhoto = document.getElementById('removePhoto'); const photoUpload = document.getElementById('photoUpload'); const photoFile = document.getElementById('photoFile'); + const photoPreview = document.getElementById('photoPreview'); + + if (photoFile) { + photoFile.addEventListener('change', function () { + const file = this.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = e => { + photoPreview.src = e.target.result; + photoPreview.style.display = 'block'; + }; + reader.readAsDataURL(file); + } else { + photoPreview.src = ''; + photoPreview.style.display = 'none'; + } + }); + } if (removePhoto) { removePhoto.addEventListener('change', function () { const disabled = this.checked; if (photoUpload) photoUpload.style.opacity = disabled ? '0.5' : '1'; if (photoFile) photoFile.disabled = disabled; + if (disabled) { + photoPreview.src = ''; + photoPreview.style.display = 'none'; + } }); } From 752bab666ae3921bc43a56baf759ee7cd30a4c57 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Mon, 23 Feb 2026 09:26:11 -0800 Subject: [PATCH 08/19] Improve member avatar display and layout on About/Index Renamed .staff-avatar to .person-avatar for consistency. Board and steering committee members with images now use card layouts; those without images are listed in tables. Section headers adjust dynamically based on image presence. Index page adds avatar column with image or placeholder icon for each person. Enhances visual clarity and consistency across the site. --- PC2/Views/Home/About.cshtml | 130 +++++++++++++++++++++++++--------- PC2/Views/People/Index.cshtml | 14 ++++ 2 files changed, 109 insertions(+), 35 deletions(-) diff --git a/PC2/Views/Home/About.cshtml b/PC2/Views/Home/About.cshtml index 7ad148c3..b4a49254 100644 --- a/PC2/Views/Home/About.cshtml +++ b/PC2/Views/Home/About.cshtml @@ -36,7 +36,7 @@ } /* Minimal custom CSS for circular images */ - .staff-avatar { + .person-avatar { width: 150px; height: 150px; object-fit: cover; @@ -75,7 +75,7 @@
    - @staff.Name + @staff.Name
    @staff.Name
    @if (!string.IsNullOrEmpty(staff.Title)) { @@ -143,56 +143,51 @@ }

    Our Board

    -
    -
    -
    -
    -
    Board Members
    -
    -
    -
    - - - @for (int i = 0; i < Model.Board.Count; i++) - { - - - - } - -
    -

    @Model.Board[i].Name

    - @if (Model.Board[i].Title != null) - { -

    @Model.Board[i].Title

    - } -

    Member since @Model.Board[i].MembershipStart

    -
    + + @if (Model.Board.Any(b => !string.IsNullOrEmpty(b.ImageUrl))) + { +
    + @foreach (var board in Model.Board.Where(b => !string.IsNullOrEmpty(b.ImageUrl))) + { +
    +
    +
    + @board.Name +
    @board.Name
    + @if (!string.IsNullOrEmpty(board.Title)) + { +

    @board.Title

    + } +

    Member since @board.MembershipStart

    +
    -
    + }
    -
    + } - @if (Model.SteeringCommittee.Count > 0) + @if (Model.Board.Any(b => string.IsNullOrEmpty(b.ImageUrl))) { -

    Our Steering Committee

    -
    Steering Committee Members
    +
    @(Model.Board.Any(b => !string.IsNullOrEmpty(b.ImageUrl)) ? "Additional Board Members" : "Board Members")
    - @for (int i = 0; i < Model.SteeringCommittee.Count; i++) + @foreach (var board in Model.Board.Where(b => string.IsNullOrEmpty(b.ImageUrl))) { } @@ -204,4 +199,69 @@ } + + @if (Model.SteeringCommittee.Count > 0) + { +

    Our Steering Committee

    + + @if (Model.SteeringCommittee.Any(sc => !string.IsNullOrEmpty(sc.ImageUrl))) + { +
    + @foreach (var member in Model.SteeringCommittee.Where(sc => !string.IsNullOrEmpty(sc.ImageUrl))) + { +
    +
    +
    + @member.Name +
    @member.Name
    + @if (!string.IsNullOrEmpty(member.Title)) + { +

    @member.Title

    + } +
    +
    +
    + } +
    + } + + @if (Model.SteeringCommittee.Any(sc => string.IsNullOrEmpty(sc.ImageUrl))) + { +
    +
    +
    +
    +
    @(Model.SteeringCommittee.Any(sc => !string.IsNullOrEmpty(sc.ImageUrl)) ? "Additional Steering Committee Members" : "Steering Committee Members")
    +
    +
    +
    +
    -

    @Model.SteeringCommittee[i].Name

    -

    @Model.SteeringCommittee[i].Title

    +

    @board.Name

    + @if (!string.IsNullOrEmpty(board.Title)) + { +

    @board.Title

    + } +

    Member since @board.MembershipStart

    + + @foreach (var member in Model.SteeringCommittee.Where(sc => string.IsNullOrEmpty(sc.ImageUrl))) + { + + + + } + +
    +

    @member.Name

    + @if (!string.IsNullOrEmpty(member.Title)) + { +

    @member.Title

    + } +
    +
    +
    +
    +
    +
    + } + } +
    +
    +
    +
    +
    + }
    \ No newline at end of file diff --git a/PC2/Views/People/Index.cshtml b/PC2/Views/People/Index.cshtml index cb146f2d..05f3a742 100644 --- a/PC2/Views/People/Index.cshtml +++ b/PC2/Views/People/Index.cshtml @@ -21,6 +21,7 @@ + @if (type == PersonType.Staff) @@ -40,6 +41,19 @@ @foreach (var item in Model) { + @if (type == PersonType.Staff) From 682f2860f68fb6bcd521482d672e94321b931116 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Mon, 23 Feb 2026 09:31:01 -0800 Subject: [PATCH 09/19] Update PriorityOrder field logic in Create/Edit views Changed PriorityOrder input to hidden for SteeringCommittee (set to 1). For other types, replaced number input with select options. Staff shows "Director" and "Other"; others show "Board Chair", "Treasurer", and "Other". --- PC2/Views/People/Create.cshtml | 49 ++++++++++++++++++---------------- PC2/Views/People/Edit.cshtml | 49 ++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/PC2/Views/People/Create.cshtml b/PC2/Views/People/Create.cshtml index 0f1bbb6d..2c876085 100644 --- a/PC2/Views/People/Create.cshtml +++ b/PC2/Views/People/Create.cshtml @@ -83,29 +83,32 @@ -
    - - @if (Model.Type == PersonType.Staff) - { - - } - else if (Model.Type == PersonType.Board) - { - - } - else - { - - } - -
    + @if (Model.Type != PersonType.SteeringCommittee) + { +
    + + @if (Model.Type == PersonType.Staff) + { + + } + else + { + + } + +
    + } + else + { + + }
    diff --git a/PC2/Views/People/Edit.cshtml b/PC2/Views/People/Edit.cshtml index 7e59ad91..78ca8df2 100644 --- a/PC2/Views/People/Edit.cshtml +++ b/PC2/Views/People/Edit.cshtml @@ -99,29 +99,32 @@
    -
    - - @if (Model.Type == PersonType.Staff) - { - - } - else if (Model.Type == PersonType.Board) - { - - } - else - { - - } - -
    + @if (Model.Type != PersonType.SteeringCommittee) + { +
    + + @if (Model.Type == PersonType.Staff) + { + + } + else + { + + } + +
    + } + else + { + + }
    From eca6998e359beaa999d71d7c9772fab4d77dd8eb Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Mon, 23 Feb 2026 09:59:07 -0800 Subject: [PATCH 10/19] Add logging and image resize params to PeopleController Replaced console error output with ILogger-based logging for image upload and deletion errors. Updated image resizing to use explicit 250x250 dimensions during upload. Injected ILogger via constructor. --- PC2/Controllers/PeopleController.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/PC2/Controllers/PeopleController.cs b/PC2/Controllers/PeopleController.cs index d8b8c818..632233b9 100644 --- a/PC2/Controllers/PeopleController.cs +++ b/PC2/Controllers/PeopleController.cs @@ -13,12 +13,14 @@ public class PeopleController : Controller private readonly ApplicationDbContext _context; private readonly AzureBlobUploader _azureBlobUploader; private readonly ImageService _imageService; + private readonly ILogger _logger; - public PeopleController(ApplicationDbContext context, AzureBlobUploader azureBlobUploader, ImageService imageService) + public PeopleController(ApplicationDbContext context, AzureBlobUploader azureBlobUploader, ImageService imageService, ILogger logger) { _context = context; _azureBlobUploader = azureBlobUploader; _imageService = imageService; + _logger = logger; } public async Task Index(PersonType type) @@ -225,13 +227,13 @@ private async Task HandlePhotoUpload(IFormFile? photoFile, People person, int? p await RemovePersonPhoto(person); var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, personId ?? 0); - using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream()); + using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream(), 250, 250); var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); person.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); } catch (Exception ex) { - Console.WriteLine($"Error handling image upload: {ex.Message}"); + _logger.LogError(ex, "Error handling image upload."); } } @@ -247,7 +249,7 @@ private async Task RemovePersonPhoto(People person) } catch (Exception ex) { - Console.WriteLine($"Error deleting photo: {ex.Message}"); + _logger.LogError(ex, "Error deleting photo."); } person.ImageUrl = null; From b43a6409d8aaa500b54badb06cf7b186d1f717d7 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Mon, 23 Feb 2026 12:33:04 -0800 Subject: [PATCH 11/19] Increase resized image dimensions to 350x350 in controller Updated the PeopleController to resize uploaded images to 350x350 pixels instead of 250x250 before uploading. This change ensures higher resolution profile images. --- PC2/Controllers/PeopleController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PC2/Controllers/PeopleController.cs b/PC2/Controllers/PeopleController.cs index 632233b9..98849279 100644 --- a/PC2/Controllers/PeopleController.cs +++ b/PC2/Controllers/PeopleController.cs @@ -227,7 +227,7 @@ private async Task HandlePhotoUpload(IFormFile? photoFile, People person, int? p await RemovePersonPhoto(person); var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, personId ?? 0); - using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream(), 250, 250); + using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream(), 350, 350); var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); person.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); } From f71adfe9b6f9a01a7f4eda984b759a56d32106e8 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Tue, 24 Feb 2026 08:34:48 -0800 Subject: [PATCH 12/19] Remove staff header images from About page Deleted staff image section in About.cshtml and removed all associated image files from the project. --- PC2/Views/Home/About.cshtml | 16 ---------------- PC2/wwwroot/images/about/au1.jpg | Bin 11250 -> 0 bytes PC2/wwwroot/images/about/au10.png | Bin 32662 -> 0 bytes PC2/wwwroot/images/about/au2.jpg | Bin 18398 -> 0 bytes PC2/wwwroot/images/about/au3.jpg | Bin 23314 -> 0 bytes PC2/wwwroot/images/about/au4.jpg | Bin 8400 -> 0 bytes PC2/wwwroot/images/about/au5.png | Bin 23369 -> 0 bytes PC2/wwwroot/images/about/au6.jpg | Bin 10761 -> 0 bytes PC2/wwwroot/images/about/au7.jpg | Bin 10668 -> 0 bytes PC2/wwwroot/images/about/au8.jpg | Bin 31348 -> 0 bytes PC2/wwwroot/images/about/au9.png | Bin 23995 -> 0 bytes 11 files changed, 16 deletions(-) delete mode 100644 PC2/wwwroot/images/about/au1.jpg delete mode 100644 PC2/wwwroot/images/about/au10.png delete mode 100644 PC2/wwwroot/images/about/au2.jpg delete mode 100644 PC2/wwwroot/images/about/au3.jpg delete mode 100644 PC2/wwwroot/images/about/au4.jpg delete mode 100644 PC2/wwwroot/images/about/au5.png delete mode 100644 PC2/wwwroot/images/about/au6.jpg delete mode 100644 PC2/wwwroot/images/about/au7.jpg delete mode 100644 PC2/wwwroot/images/about/au8.jpg delete mode 100644 PC2/wwwroot/images/about/au9.png diff --git a/PC2/Views/Home/About.cshtml b/PC2/Views/Home/About.cshtml index b4a49254..96e3af30 100644 --- a/PC2/Views/Home/About.cshtml +++ b/PC2/Views/Home/About.cshtml @@ -44,22 +44,6 @@ } - -
    -
    - BethAnn Garteiz, smiling in a professional headshot - Cary Vazquez, wearing a suit and tie in a formal portrait - Christopher Cleveland, casual portrait with a relaxed expression - Dale Golder, presenting at a conference - PC2 employee - Jauna Balderston-Todd, standing outdoors with a friendly expression - Kristin Loos, seated at a desk with a welcoming smile - PC2 employee - Michele Lehosky - Nathan Becker -
    -
    -

    @ViewData["Title"]

    diff --git a/PC2/wwwroot/images/about/au1.jpg b/PC2/wwwroot/images/about/au1.jpg deleted file mode 100644 index 6dafbf7e545846c10105b0492f2292cdaed767d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11250 zcmb_?WmJ@3`|dLg-7O&L3{nEpIdl!(-3`(m(t=1g5`r`X0@5AQAcDXEA}t^-4N`LO zeb+g^_dOrZT4$~QJ!_u1=H7Q)``#b+_2IeycE1V`D$2p-01yZOKo1|_{s+!)xU{sH zx~7^OTv_&C3pm@rgP}Zpon5>iuuK znEhKF0H)dhK3RUlmTOLQs{N#{H%23b@c#P#A4X_x<7xFkQ05`V-}0gP1MYdiq?X#6 z(hnF30GPPe&eqldfVKMIkGA_)47UB>7`y+7aq@O{008WlfBnuj7M>3{=>bD6U$}Wa z;D*0`kALS5{|o;|u9uC~Lp|)p2S2Wlx6MN@?7wq)?d$b77w7N(Nv+&G{^C~;Sis8O z;voj7;{iYWH~$9&0Cb*z2MCvy=I0XP=V#|;=c2cCvaotV?`dV>^tbB&wEaD+f7gV* z2LOTxZcP(b-X1>x_M!g$K?AS>0)P~t1ZV*Wzznbh+<*We0!RR|fFhs?8B@}HG6BIiXca#8>S11W6Z&3;vWIdG2EaIAaxep!9V`fz2CIP$!M0!za4b^ z7Um5WAr>Q+FqRsYHI_eCB33b08`cEYCe{r$5jGPx3|kx95jza~Ep|2b0QMsGDGmSz8%W!*e=W$Q*@bI4EN#GgcdEq7C zA@KU}mhpb$li+jU!||>0L-2F(oA9Ube-U62FcL@-m=XjKWDwL7Oc3l7Vi7(ilqR$w z3?|GWY$co{JSQR{;v!Nbawdu+svsI6+9t*zeo8D$Y)$-%xQMuqc%1}H0wIwmu_B2e zDJB^p*(60LWg>->I+DhcR*{aA9+44~@sjD1`H|(2b&{=;gUO$g!^xe<6UpnyXUVT0 z(L9oTWb^3Fqv}Ufk1i=5Q%F$QP{dHwP|Q$VQ_@q)Qof)}rfjBMqC%x&rP84CrOKlk zpxS>-`dH+#)#I4Qb&tPOqfoO_Yf=YN7g3K5NwDb z#1@hU>4h9Y>7Xjmm(WV+cSbBmK}LJVOvWL`%co3F^`5?d+Wd5ziGm5v6u?x;w8)Id zEY9rCT*!=ML1z(UdBO6IAixbLe$eGMJ#QEnr-*czu#m^VGh`AKG!nxYGPPkdQt+;cz zkvw=jay%hCEj-7(Y`iwSdA#5FNcdFvUi0x2(Q*hOB5RETVgGKt!V7K^UKp1>?%1+Y~ydNFgc_hPH! z4B{5zh2lRXAQILRr4n0`ERv3rA0&TCJ(u#3YLvQ^7M2c@?vVk@$jijYjLQd0ox zF3UZUvy-coJCf&<50vjwKvPgsNLHAG)4;9Z2>6krfMSqhzY?yJmQuFT4`o(m59Kx$ z6ct64RFy?lMpYNpW;H+!u9m8{q|T)7uHLSJuA#1xqw!OdOY^1XkQRxSnO23?g|?J- zqV|FgvyQh;uP%YEu`WXQQcp%NMQ>G~Q$I-mvjLTXgF%ZShM}%uso}YitWmnrrZJy! zl<}+ylZl_nh$)q+lWC_JftiI_y*Zk>o_U4&t%aIJp~bl++%nhl$V%EO(`wIJ!aB`* z#|CDTVzXrnvrVzxwu9NF+U?kj+h^GCJ4icZI~+SIIOaQEzEFEn_Tt`2&#A^4)7jj) z)rHvQg-gFHt*f8wq#K)Cl-sJiuzR}uk%y8;sVB(O#IwbV^x=XT^Jeyr^j`B3^U3k~ z?W^ls??>e4;y32c>i@=nD?l!wBoH;wDzNt@!^^OjYeAAh1;L-=ccXou+WH#~2$-u#KN zjv0-89-9$+8)p?a8qXE~HvT@rHeo!GKQS)}J;^0$Hd#EmJcTGFAZ0yOCABGyJ}oBg zEZr=9B!f32KNCCCJ9G7|^4pdyXjW3zZMJ>(Opa7eO)horo7~HH*6*hBB=V~BY4T(9 zuirbo|5hMh&{X)eFrx^)$fxLMv2O8DiC_t$l&Ung^sdaMY_(joydNQeKvYmyBvztS zdROjzF#hnhN~Wr%n!UQ9hP)>BBkWk2g&SOtgFz z`r0-rI@vWPKJ{r@W_l0_M}D4Botd1~nf*3rJh%GI`rG!r)BN#wpYJydp^IpXF-wF? z8Ozkmg)1y8HLLuqoog~{V?VThEUa6t?`?Q)+-|=5iT5*Oi*5_C&Ar{RBe(N)*JyWh z&u#B!Kk9(!AomyZuZBa3!?7d%qs?QFyQvCIO1=12gb`2LUYp^f^#Tm4(N{R{txzWgs5DF5Vv z$pHY&^Ppaf|M9sN0)XK8gFngB!H)iKrnIIu{eR4XUDL+a#>2+tVXL|hHogunb`Se^ zv3BrsaCM<~aQUm={u$GMb^agt^AFE|G_#kD--E7v;OXk-@8Mu)??n%H@v`x7vG~i# z!s%b9lpTgSYcDdhX}Xg#f_QmWTUalt8#L4>E7%{{HkS0AQp6z+K|~{Y}CB z{oTVXzzYD-<9)vXNCRM$zxMYDey|5I^w%)a(b3ScFtM?*FtM<(aS3p-aqw}lu<(fR z@CgVBi3qWAiAjhFNggoa-y)#DnP61RheSdgESv}L|EJye07RHT0^ke=5dkPfATSZ= zzW+g*0uQVnbmadzb#x3gOcXFG76`z8SeGXRP##*NfYHIIsMt6U+Xg)>q7eb;48&X* zB$Aj7vP|+Sr zp#vD053Ae{l>YAUp%EtdU$X^3M1LDGa1oP8l0MNuhj2?JlRur5;)i1i$4|&3QRRBS9O_gqBZ_ZUAGr-$PE0Tg2NEK4T zIK(P7tsoMr*nLs03G$-kE=@2j2@r?l8PciSep&q9)WP{kw61S9b;xY)5X*sV^iLyF zL25S*({vH(NcTI~@_cn9m9bXjsjy!hwH-7y?s70hH8@}?bi&`~9*~mC$dF0P&{a(x ze_Wh6(Sr1_>ckI5kD6W5`h%5nthU8{_9_d6E;+#->VGg%9oin6ldV={e-D_N_75GH zmRf~pBT(r|8^zqj?s79L9cRTt_9AXs*6xAe%%89Ir4#mByy;9B5IqjMFC9Zf`b9nn z4(&S>F#U=UpHMqH-2B>fbD7B+s;jUEl-m-9mM#4hwJHZi#GzeBEJln+o3p2qPC`u_ z-_dgoF%<(qU#NKCW$;VRER@h43iq9RVGu z??#OxXVD8Imk>5g{N)P*RiS-}ok71XnnoB)oZHDA-x|+4P0=;4;aH4%RNm7G(a_?c z77Xo%Hf!H%zGT}aB%V>#)O5dcS|asur68O%p+RuRB7aF}*rLkB(IPsfl9n1n*SXkN z5j)3ElGpUCVuZ6v*$)B>y}~rkskTZ6v=5`lJru(pP6lHL!&S#9zwqv<9 z=J$&B8!{{+(UV%t8$@xbrw7AZ!UkDdC3NPKURB~cLyD&=)W~)g1huRZt>zPLU#%P~aSbTFsoW-6%l`&OxaH=$+eyCh0l8^#iGQGASr^Jyht5-lcBR*~7> zO+&+kpx}xZ4ZbIsQcYNWL$rgFB+EgLz z=Qu|6hcV2R+rdP!5GY7QP5$QQjtJLuYT@5GvD$6d*Fe$+v1Z2d>w_R6Jw{ zpPyOUPIUM-@KY&O{hXKIn4660kk6t8G4Dpn*jb~JS<``WlWU2ee1a814@-wd`A9to`?JqYfQ{AYu5GVt3it4zPu== zBwz%Rdptbtm4u_#qZ0h>BKC|#MO1@FmI8CKT8cL1y_^I9P952ZTZiIlF_GD@4}Rxw z3{iX$6v_Yd+q9%Q%Rz&OJRhkC)hnFjNctmgB`#d7AynK%jp`R{BxS$lw+YJrlp1)k z9_k#tB+};fY9!tJ{XIQK=MV{+*t-XQebh_{zvI=>uF$$8U}*Sy+hs6e;|Jc?ygCd* zoZSOD=xIN3o#O8L^XEvdrWGM>DRs`H@@>EGNRtQVs2{(b72J9iuD+Rofzvad_q}`P zc!%6X^?V4eATJy?AdAOa{;^M%SBG>em>Kd$;|)I62sJsX8z!uwh)Kf}jO(PJIZpK` zPx0-J(RKx}Z-x}RbkV9YHjRDXC;e{StEK-uEyXf@*p(>hdOLV5^7aemYW<_l_8UN z_F=8t57trPP<5&lk`VHg-_bflFa@Gs6ajTCFc=v{e@5y}=Vb>`^uX>gNl-&w-dRI( zeWLM?-1u;cU|uhMxGO5Sy^&Y-{F0epEW?a0EiVI!O36>jzo5*$<__Rlv%Oj{%5<65 zS#aU?Zb$)iWqY=R$5P7~lLyiDL{l)K)CoBR?ABVX<3H8+#lQr7tWcfEP3xq!4SduT z+PsqA{?s?E5FrTdtK5ns&MnV`gW8>DHZ5fqG?)$5^D@L6lit%rmNY^rjUZ}MptTk1 zwsG`J8uZlamFb-ND`Q2Xg=WiNMlg@g=U#j?a{6@v8Ip?`&@CZqUkahaVqf>_X6Bow z_;Y-+TkDGBt&QV&|5A%TS@*zv)kJtkDuV_ij{&w zJ0mnESHrYj*=CydQ|T@4_KPz-%n6djY=Om*m%q<;_kpYYyjsM^bk+VUn`M#4>{3Im zX)QHT*W8Ud+_l6TeY4uhmKll})gQMmm679)vCf{s)J0cm)7(}ymFw{d?%N$Cw<6@b z>XwseAti{$1;@h~tv-b#0kwn2*uNwxB|o1BtBk{csmAn|G`FyOgN;H4MyqOl4d0~) zB?yeH}`^XuGAEvHVl z&wR5}lOkZKMhYC-P#~{I`3ZK%r#|XYSt4b3TJLofGO*O{Sl(fF%eZ{eZbR&!uG84= z(S%8_yj~5beO}QKmLhRbK##1;8%DMs4MOI=-?`ItNfDL!S1H{C(zxEtHZC-c0TtC5 z71=A$B~!NPfx#Jwq@@)>BY3MmSxMv2=+mEp1X0)QR z13TqMedKIhFKSb-wj_*8-rq>h&LvwTNY7`gNWpYO*h|Obe&<(v*yZlb3>dvdPScH2 z-#8q!NIzz=GQi1-e<9%XnmUDW!RZ@24qoTAo62t*w&B)s)RWztJ_lJ;GhgQd=kANLYi(#=5yI7<&L* zlED>W`tB1l9_4D0e$?)zf>+{CIw~0V;X?;(>rl37rFFeG$mEK0EZUuxj|VHu6{7dR z1l9beA%oRB#DT;5Ifo4Gq!{F^xoEK@l`hAdZoT|mH!;A6wP`5QD92sLpMU07)cl8G zMT~3$^YKc{=a40J_#a`d6#^fe)K$~olA{}kg^L5eUc)*^N4Am-g$&UVPBnEMtOoSIgRni!%v{PBbHh?7o8XEzw<`{Q2ryT*#`ok*C~}t0Q*gvSDB1WyPSvQ?&_wT(N5XO z`mz!BR5#?ujMcHU-+_h0;u+6W^iU489zF&E!4JKs3 zzX$kXoa;;M>=^S5ON?`?@?6rfQzooopCBB|WlJyEE>yn-v-0f?aFTtv2iDMXJ|`?V z7Gyg>68uNKCwR`dQ>-+T!$voAFTrRh1S#*LWn;T;2m-05G$^Z038tsY7*yDoy9C(( zBpxKvnaES4u-YgjE-w#>jYavZL^v!}nXj_S2bQOC(%b{~a3+0`?Ric1%DaSH2#IB0 z*ClbH+5q|=E7QZm0J(Jjky?L?5Ufa=%s7IkjLYy|Y&NuKXrkren1hcF%3TzqEy&mj zXnNK+5i;+irma%!WD=h1@9xkh2d=9w#IUcKKN8Mo4OfEho+7qWD2YGRd*uZt@#E7vhNNSM-Id{Q~FCV%x@LYIcYgIjVvVq8#2W2&?Kq^Tk>*{3b2 zQV&X&CX!Oxlu7x9vH5Gq`)k_*t39WezXgYcu{OP01awkz7fnV-J1XMKf-Mh-gCbokixQksA2i)(NM)6QW~xP-I>H)Y&#kN`$J)wU-dWX+oT~ zZ@Wl#x=?RrR@uQ`xUwMm`iD<(X?9nRAh*%(Mm%msptt<F2;sQyxUm22~%Zu>#T7U{GSnK!!#hAlk?zU1d{>~j|SP||oD(zx2|_;%KH2~9=k z_3lfmuZqSh&SkgK(@2sylrCFJI8O5P3UG<6&;#M=DP@He%ruB9E&3RJ$XH55+pUNa z?QkEG6_BD!35R_RmJU0ksZ4#NxV&SgKHDd^^h5W2QN4=qBqM+bH+SHP*Ex8E!ih{Y zG)Rt5k&I>E<(J?hWPbXKWN`U0c0j;he8p$tsXnKu3@w`3Hp932&l;8Uqrw=pPiacM zECh4nI)RFFB7jY168{{~A_ay%UmnI+uvsMn~NAq&d^A z1J*d?U0s}=Pj?mb{*S?wce%mI^Rq|bcg-Cg{D?IA6@E2ULj=aD@bWvO$JcSEg{?!Eb`bC(M}33OW{cZ3Y7vD&oh*dbVTu zlmqe`--@zT`Vj`t&`$rs-lxN9E}e2-X%^tvZTar#OxkW6wb95kq9H^SyfU&&_#sG6 zchA~MHf)5-ZgQ6;v*$?#l|s3ZruH#yR7UB%iD>UbG8T==&V=*n}R#|ew>RxKK zyHV=Lz7h2Th5dyt%0rMmb!Mx}juM?sz_1l0N$rY|I$Q$wNw}<<>iYV}6a3EK8L&qC z;ubC4>B-J&8~fgb8UfbPdG^$!jExlknd{wQbIy`q4Aq%NJ{xTsN-l`q^7ve?K@a1E z5XpGH!J!0+I=WY!okbncPLpkiOPd7QyqI};Xu9q+8wUg~)u@@E@zFZ&`H&f_1f)LB z_*E0(#(G;b{ayAYp6Sps`HOfHnY6QJJtY+<*KePMcI4O=*jVH+rt`BVDRJ6eEibsb z=G}IG)ebIePg}fpFMg+8^OPg=NNU2j6!K=B@*dFVqw%`UjFMN;qti9HxtNLhI5qEG zp>e1^YM?3ywK1l9MI7Y8vsaTRIYUh1Lf5CMq~lRunvk82Co`?nU`a6(Sl@nB0TQiU z7tUs`*i_8ct{*Q^-G~25B_*d)+;^PKTu>Hdc4ej@K-t)1JHxWmD@^sMGPsC;tXyW*rjBS8Q- zBC+P^>pf8Sp{keG%JLrYIjmag-V^=&Zq_pE8H3yt!6C#@O<723!eA5P$YL?+vjy_| zKKZG~2lHf2hV-nJOnPGfvsJqHVBKL=3*sDm!j)jAUuJ}M_KuEJ3BxhO2}_umrJ_ZT z(!%B#Y~#)F-JZ9oOHTf785JM?o=$&_)Ry_o$6JG2+onWa?snYv+{QfEA{4TVp7;vM zWB)Dix6y%8i$IXqf?dDLU5h;@%M2smCs!0<;Z63?=i4u{c((P6XqTXX-<_FE#l+H} zNB0r>pfk_+mZ;*n*snQcfh+E!9xNP80m8*Mp=Vo5gJSC#!at3wUw6c7mKICS>KL&~ z!`<|vshSbsZD77hk%v z-}0V&GmaL@frJE6b01#xfV0O#3l~~uc&{kiQdqlV^SD+zbShKQm60O0Lc$34>Yk1< z3=-t=>tGMW=+HOM4A`&CQmwLSu3y2j20TgkK)>l{l&h-VxRY;mkA`p0wkH}|1jX?S z+mAV5NYmUP)sXdTM`ZYn-J<-Gen~4VVE)^k5csCiy9Z5spp`?luRQXW{T%f*LtJ|U# zA%NP%(s*-S*>c~9>8B`#RO;4gTvq^ZuV|#R{TyGfY#G234*w=iBq-#w>b{b2*U1;4 zT+xI#hR@JlFa}MgxAWbV5?@gMcw9^v6j{aH2Ss}y8MNwW z{<1y_BMBz*BD1x`WT>*VQG8K&*3MlC0xR@2l2nqeD>pb6rAuu2m|VZ~ig5K7!fIUe zMeDSCTEC)XIb_?)xUd_El*bc172lqD8x;7WCW-2gs-pWd-Dv}j8^xGHsQ7p2E!(wK zq6*wdk^TJ2cAjO7^!3I2VbHGN?_x`lAfV^Ak>!)XqmR)Y({R)ziPI~Xa`IIRMlm_I z5o%cSOmd3Yb)DrGxDWY2?S;)zLR8<6{OB7cr{DETA+<*K+spGFB_;Md9Nu396l|ZV z;wsnmPf7iXoDbSE#$!buXUJ9rOP=l5DEC&aG*kSzo$eFyB} zcABozb~2{>i*5HN*mA752qSMU+VfD!4qd+&HO>C2gmta6r^Mg3vQoW`2@!gu9j)3L zUC;f|Gy|p&wFbWytll(pGDHS`q6pB$KqST0KXPro2i$sY8!pl)U%ap*7Ruz)VMl^` zIfR&Ys#YlvF4?#m>K7|hs;#06Xxz*HOs*$(pMcc^z8|XCjS52a^&6a^(r-Yh)OTHG z?Ec~rBDz7T8_O2BEkj60#jcuyp>b=_`;igp4k#0Pl~RI#HrzZvL=CZrHL}>PC`1 z&lBx>OIG)z^|>tsZ?ufJP)3t}`9f0l&^>@lOR$;5$I`=nQ}aHn)(QKN=rX>>(x}n_ zQQaKVva{xIo}r`ahEl=CPfEN$CRG8crRU^VexzN{~+_E>kS;JL$;2Y$6#OZiQyYSf~J?q^J|FTG#=!z+R zB5n8cnbUB3a*(L~G)I7ovw7<%_hESLJ?&KoU_sX1w=kEO}GsNba3=a|?`z-iJL zQrYS6g=g$&Ux-#3`9ogLd|MbFYp7ql;^Ryvy>O$rt^8r#R?ISi%hHXcMd7#fc7ecI zxB+S10tb=C!HaZi-V=)AisIDU<>K(#nzdd_U1A2!6NX3J8Qad(2jXvNg1_c*=s(#bX&OfBttMwF&YC-Q*JmukBCF=vVLuu%U>k2+yHTsB4C16qxxBZ{!Ve~bjG+%+z*#VPKCQw<}2@x7g?fr9uvFYM5`gg5`UoAhT^P* z_AWfZ*bDvzXF6T~ZDkt%>=W~{U7jI*iJG^9U@r#gmDkS2=HrmWvz)@a%dA*L^@W+f= zo=Dz(CgY^9Qez@aw<`Hc{^BsTS4qD9-P6s*<%vI(CcB5HI!5-c4Z5R&FnFtGLo|I% zc+!xL8nx`pkSFR+m&mNPtZHEd z*}86W6i+xleh?3$n64Y+na7Y@n`q^;cv9lb&p^SVdphlVE3oh+GYf1X#Yb1C|0G}7 zH=cAYz^L7v{EwBUtI4*tiSm9q;mc={3fB>zW}kz7HYj_9iS)YA`E(U6(0KflKg7XS zMRtnyC{t#$CbB*^6|}aL zrWuVV{pTt-3YrzXUZ?bS@nok+sZc3#R@*#$S6z(&{1uXy7RO41IVDe5kIj7HfmO{_ zD(p}PH8MVSDmaTjF-4&cK&-S%7PhI zzoHqp!bI9f8Rw{xA0?x>Y(zHbZC$Kq#PvQ`_A!_7xgz2^wepJ7&O^g^daxjwW@W{4 U6rX1tJ@_6t-C;0+G2PGqFF0jzn*aa+ diff --git a/PC2/wwwroot/images/about/au10.png b/PC2/wwwroot/images/about/au10.png deleted file mode 100644 index 337cb9f98124f46b33c69090f28977f11431b0e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32662 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 W#s-(tTN5b&0000oqQy(G9-h6=``i0H z7w5bAMv}Eg#vC^1y2yXdG4gl$?=}EaURq8X00aU6z_%CR?=H%NoP>n2s=A7_oTAjf z22g$i-U#&ead32ZRhJPb*U{A@M_2*C0Ehv&0BXQH6EipGe{}vo^ycq>=zj4Z?*PCY z%Rg6!2jR`SYe=oP_~ZUq7(d!Svi~2#G`DawdrMIAO~=RdE%+PX`^Mv#YN|`T@$&!x zBC5HAxj6uUwEZTJvig?}vh_bYR{y1A@8Mtr03iGSBX_VcaeL#F-gs(LJ7@PdzUd#i z>%VPB{>A^7t-FQUn?L0BH#w@Ohs9eg3ZXt|DFFE2LQZt`)2{=q$GIQ`FVI)I9S-pP3=w0?8x2BOzi*h`hT1Lc~<}SN&N)? zz}eY0Ga?@fDyn9 zU=45pxB|QY{(ulb1RxfW1V{(u015ylfJ#6upaIYd=mLBP3<1UgKL87WHNZCD0B{Pp z1l$9jfIuJu5Dkb8Bm}+#(g2x&96&zc2cQ&C5vUH-1%3ot0Ud!JK!0EuFcz2s%mEex zD}nXEcHnp5C~yY20^9)}1FwOPAQ%uT2nR$8q6NJN@qxrZ3Ls68A;=2k0`diY0wsX5 zKt-TxPz&fgXdJWv+6JA1?qC2gC@^?16fi6>d@vF)DlmF5mN2d`fiN*J88AgKwJ;qp z!!UC&+c0M^e_-KZv0=$!SzrZWWnm$(X0R@>fv|C~Ij|M5EwBTybFe$Gm$0vJXmBKO zOmG5la&S6uR&ZW$5pWrBWpK@KgK!IQ2XOcB@bGx>bnv|Jvhcd_w(x%Naq#)@b@09L zGw^%xcL)dwga}Ls!U!q|CJ62b5eV4`)d)QZvj_(W4~Qs;?+`f6mhS#h=ChO+ZW_N?<_{Lr_PsK=4RNPAElaN0?05O1MD;OT|h>NTN>SM^a2OL2^e*Mk+(F%c;e*%hS{H5V-tJrZLOvlXiryAWp+cNK3I|1BXP5g^ek2_q>Z86!C*g(U@% z%8}ZXrj)jlu9Ci#;g<1}>6L|(Rgg`VU6Lb}Gn1>7yOigZ50D>FKvmFC$Who;WK?uj z>`($J$t$HQtt-#{nlYCvT@2XC~(e=WQ1Omvom)R|VHHH=vuLTbn!1+Yiiy2fatQ$ByR* z&s@(3FDP0H-n^ zq}`?)r;lcEWfWv0XL@9AXDMd2Wm9J-Wk2Uw=Pcxk=ho$sunw;-_AB74$JdkkkM)ZU3JraYe2ukDG);NU*v&C5z!smD z%U0{w?Ka)EnRfa1z7D~T=1$hm@-B+5+;6zw61$PRKXw1@@$I?mb?!a;ZuNby&$w@` zU#EY5Ky6@ZP+@RnNP4J$SZugwM0liYRA97yjBl)UoM*gcf_tKQl6$gwif5{2ns>VG z2mgoFUc z8yTAsb2u$BF*Q z!Kw4<(^wRaW{lF<+oh7eRryNoA(a)PrqXx2p=l` z@ckKn)P6jA@_I&i&U#^Z>3CImUH|Lw_wRpU6V^9aLjE6IB57f1;$iPj{tsZ0aI$yu za3$ApHZijxXCeOw#QYmT{9n8N2d9{30|2ez006=2e`IfARR107-?;5x{D0xg|CI*l zKXx#9000c<8+tAIkIbnE0N}fOlP9^^Sdsr@DWR@O{$K0BqHbYn;cDUdHdQSf3ojc- ztGD?(n%lVBI60ErIR1m){S*#$MkeTcuVz)a4;#>Ni-xLI;?{3|6Os^lBY+xq+Wh86%oNCN;~ z694{vD*XHRb8C z01pF)fCvP^A_3lpg~4y0KyW}fcvx6ignz;VL0}jP04y99yBIt+rJ4x>4u?y~B~@}^ zJ+Au96}7nOcRX%0^G_*hJAYRJs4&2{Nx*;sB7jAR=UM}d1eKHpk@|`^5`&|-kZd@9 z!bt8})qyXwSSZIjj)pX>MEm-Nr}6QPINUpWS>u9tPS-|ZSNeLx(S2SYSy~)0eiOtX zB~h0B1vG74pAI<_2zv zl+PifLF?b?o}Tz5zRy7X){IXz=$%JrC{Ce9CVu4LMYC_v_MrCs=V0&Q)Qyai%88JX zZovD3(I72$40QgK)VkFd&=LCD8b;bgS6~A!WL-QdU>Sh;KNt~HmVeTe`V08bI{z3e zCm_IqIMxP*exl9qhC4tQa~j3V~iOx&?ld`n7Z% zKaQIU9`bM7jS3-jMoW{;{B19YOv$?T>d*M{`$U)Q+pYtmLJazGLq+)|wBY>#1fnth z>*+55ynH`Q(XTS$0H)S2IG!E@4xo=Q#ZETuS0I-j zcuI<2>$7fpZVf-)VYp(|xyTJ_OLzd^(HnSfjs0r*S^6BfMdt55p?`c^5(Qc)RX4n+ z>fxv{RP|&V`!pD4kCFn=57d+N7<87rhr>XWyH>+8$;@9`GmowiN__MAAJ3Ke7J0jB+w=sRzeivZ-9_r_~UN&SKn7oos zRtT;eVC_D}a(%W6_>H;QkG>u~D&t7+-4OZ#?mkgG| zQ3Hz*2mA#j?$z$dI4B_+8FovD+H9w(#9TpEz^V&gO6l4bTNP<0N&OV;yENvdBWv3sp>d`79_T&Z8=7MyO9vZW|{F}-Ir@Q$vundq(3W&??KFc<@qT7rF0U4 zJ#-RN61xMzgaoenD82p#gd3$@uo}rezIYcX()~jFs427cAt>u+Ak6wN;J2DcOaIm&o9D(=W%}hzJU^RzfMD1Ojt6IvW15R^$_F= zFI*7#87aU;cq0{2l|AX#-8@EN*qlFHZcQ?iXCN-+ye^V_E)=*<=91zo8q@hRzE~E2 zZbvO|X-kTdV)!iOD__{Iw=2umQ8L!NSG-huHej7=XsZ&0_Ct8VOL}h66z>PU%dxHJ zWi=tV_*0u|p3mo7qU3)8DLyAG$FwirJtby^9KR4D3h?_r&dip+YX=dWR8#A=j7b7t zSTUOzS92<{C|UdW_VzNbIvhLCq8EQDmK^-KOHE>DJVm) zm@H|b(#8yPgi=T9;|KOu=u?Ia33b-l=^}ntI)Y0{7oN2gQnTeuVP>Z3rnjBv`B=|z zlo(yn(suVjMm9j|1Ku|yG$;#NuZr*n5KRZ=XHf6vFa-4%*M)wBDM6E3m)<|VQp}EkCW9qx( zo7QqZyCw~l=;$f3#=w@L4U<00Ft|ac`sYTqn_L?Bjd*=ABGUf5=Btr{=&i4T5|cv? zJDL`I)VOvhmqMP-t1P8>8fBiU%e>S(D`@%favz%yBRI-O+J^nabj@cdko&VHyhsoa z_ed5neP!6sj=(<*SuL}(F!anV(tLO(jB-_q`k*bZZ-S{10T9zH1TS1WfLTD0|!lKHU>=*#~F zl#Gp?T}59OcJJG^p=@{ztt79x3YmGY+kfxdvs$>ww3SxK#KXB+VyBgJOm|>+5Scf) z79u*c4COXU*ib2|AfOANWFf&9{zRF{W0rz@j3Gk>IZ5|O#F>d3=PaJQ<()5UupqnV zU$U5K7U8XHAN`7OHB7NPQ#jaNU@)Jn$p7H#0)XmS|7nLW3|tp#)J&>KpF z6cj7p4IZ0fr=OKZ^I>Q0taZp?k42j}E0<=nRD7IeGGCJ>yvS@)JtFCtEIGcnoZ zAl{-Dm{1JifrWOb1HLsdd!lCx_g2_S8EGdLSIpEMx6 z@y877=`4fG`7VP}9I(rhs%nDCpH~{B4i0d=G`BM2le8^1gdBE6-W|`0r9Wzq)G#E{ z&5dMc6Aa|9@WH`np6F5|F;hr446oL7co@256Mw2G8xdG@g6WH(l#lU*&wxiWFvOws^&U(c^!(ODU)9k?KD2l@|-3eN_{Z!v>D2?A5<#Wb0@XVv+1Q- zoiA=QFh}}r_WMVk51ber01f;d2CV6n;-4B2jq9b9TJ>WA?cGei{-+k-u;9gY^Ggz< z`7z4w7j(xbFcJEKn=Y=?l$Wgl#gJSfLyEl?-xtI$x2fiVvF24_oU)D9{M0l_rWY(3 zu=Y;Op|cf*yvBB<^4(_=7MH&oy<5bTy9-Izmqi4PBitj7Z5wemGnTzs1LXi*sm5ww9r9R9N4^_}rYOr8 zUcVr0W%`Cy5)IX>F=OX?XN<*mn?bp1?IWV-(2Z67-L@A=88up|5Q#9kY%2~k%h^$J zo5U1ksMs;_yPg@Lp~XidLMy9`;p)1Q(xEa4lV{zAV%nY^4`VDX8rOt2u0fC+9-+_h zccgtBJZv>*p`zp1wd&8aAMl<0=tw3TBFM?j(MO_#X}*h@;tFZ&)0QY#<~r!GQ>Tho zuf#eE#211ka2aj+nLG9yWB!l~;xlM_?pM@#u1WyLTfX;8zz0jN!Q)n1Fj=DcvSvd) zFa;VmWRpQ+ZTnGRFvwoYH@di8xy&j-Ps?;(%^k2d>=6X0iOn~)VO7pu*I-YSoH)zR z6s~N+Yn^xA2<$Bybj8&OPSSUwkxp6}si?U znO#85le2IfZmUfsZIT4qv|V9mUmH0lBO|*&erk)XHTt**K zC-)QM|IE+R`<2Nb3R;A%K*k9F$nQMUE*jGEfl>eoNXm07Sk9}Zyzj9b;7{jj(?Bbq zaG=Q+f4=X>``m}8u(@nJ@FFzLor0d03z z2E38Wy2lcAi6uj1{(HGaOH9KQtl4JoMiiYWm53#1iumLHN2m2Cr6R^l*mtdK9#5}9 zpF7$u>@&B8T$cBd=_WEQB-L@RdUm`oI{yMrOCfF$dUE?gDO=MY6J-wjGN0V>VqJ!) zZGD(axlv^3Lqt*BHOM99qEnY9Zlea@S+nL@KH#yZ+pwxxXe61GEbrZ1F>qZG#s@^} zd~f@hlkV!s)Ut^7?d+xBZQw4xAv<5jV3}=98 z$vIpIiVeWUJF+Q9z@k}b>_=(CC-?_@7$AQ90Jg{>E$k(G_ji{(KZ=3P!Ry!w%h}g& zCv%Z0yM(DM?I}|E=q>k9#W7try90<3Ho7)(I8YHyM;Se-95?>dvtU0P&OG88`Kf0< zs|2y}Y9ay>Cl`O%)WXU!@zA0{7_+jXFO%f*(p70thT?+Gnb7s45IrX+BeX2O%Sx!E z(&Rbkb4D3Sbv9D#i4*&PZ4RkaKzl>nybiPPA|5%_wsJFjE1{H&lz@xVsUwFlN?81? zLQO-*@YrmIU@CL}i6`x;(l;{lu16!0os`Y*D!BP|MaR983Eyd9-`#8y8LEyw44(M5QF_ew2_zC+ataD+ zrLLkNkFB$)d@mwz1K5LsD2OLm9kOMiOwDj_ID_y09EgkUSkC*EHmtie-G+NJ;{O+5q4MmsSAE(o zQ-1v7GI}w}*b= z+sev$lSw4>i;^2|nubD2q`b@Kc&$1$YCJV~^@WQM1tMGbCVgN~6)OiufB?D?xA@@U)@W6`A+8{aZSQYZayUS`PI?=>* zTbPHgD!!SkpHAOg-T7`jQ?}ze@f`~1(mwCa5E7*AZTdAcT%Q;Q+rHv5kq{9yFcEzl z-T&T^cx!ECIm!AS!eN@B;N&gIB5NtP7(ApEJ%AOXFlbCJ`SXNTPD#cqQ+ueYpCB@B z50dkZSBql|rL%3hjN3|#L4l@xn5n@0S}?dSo>I^y*C(A);x{Xmr8uOZwPUlq`aV&( z;$&K@+p!Zyx9V&Q1!sY{Q%w;F$^{I>+QH;d58S)2octul2+vVRk{+%xB+bteH$}# zKQC&&^aEi_rvZD-ha_FxSCI;gVP7~ye?RNCUFC>*f~Y6wDJzF7`ucV4t+m}yWZTbR zUcJ|n2^w3x1nFt_6V^&|4_DIG@vqX&fexZ485$hBN_pT((xRe>d13zk{pz3xhzJAq zf>II~tNYt#q>l2Z$C2qX>U~1bj+0M1*Hc0nmn83TzSQ(H=)?+FiYTBwhZF|=Xi-2cvl2we+lpe!$- z&xKOnezOexTBvO_JixAqK27^AU~z9bzox`D#`g{a#8Bs#k#d#P#e|a6vRzTp)yGJAR{Z0Lg>A-P%c#x2%F){N zawxCKSL&?gWj%>l%=z4F%bLq;=7R7=Hj={7ojPHQHJnfT!FXYBg*3uYCMebBsnr&J zo#)WZEUC9EK;K?J*(^~Oe&_d^P2p}$7-HU}c4`8jd7t#~j?Jl8hZ}c0A&bWWHw$^} zCgPr;YybUh5hHR%(m3PbGVr#L)+OmRP3wbbYj#_N9*PrB~3*-$06g1f7Jv}4(=JK$lM|C1d9Dj$3 zFPM&%aSGoachAXT#P7q2pN4zSbNR*c;dXVDr4i>qqJX7YXH14n&`-vS>^>%CvmM$& zf2PN6%tU7CqL8nWWQGln^L0@Zapio6UF&Nqu=a~2axEvpe^A!oZHqmi-pD*BWDHtR zbY=LQu(f5OWUd#Is4YK2tD$c$;Nd<*VRx>V@Uu4;gWT1jNKsYmFkdQA2sN?AW^Vg- zx8iJGyYP|$1Hk~;ICz}jI)a94$j_3Nr;CJoOXe5 zpA2(GjwAaunUtE}w{gqdhDr$3d!)o`J*4_8}MTxFvXnagWnYD}qyg_-TWdAzy4=?!o~ zA}3&!-CLxZ4G3)g4xk7D;G1i^;BKX`J{7S({W_{XaU&-E$~QB;+|{*g-Kw>|;lsn! zaVF?=RDFEv{OYuH@ZwVaPUFs4#kkWf<47o94vR&aUvm`id$8UUEB`>g&;z2m!x%B9c3T|&@JylgCOPWlR>`nw_L9W2MbNAj-C$?rVW_=C5&>P~0Fm3Z0g}SVCT~f&in<=8}$C#ps7>mX7YhU`?oHn}bf~9}DtOd+n3}WX7(!qt%j`7A z_f>Yusk+nysd!+YX~2qY$#i}GjBB-9Qm&}Bp4htK<0I@xZU%ygwoO)$75Rh&bXZ6vp6&P8@xHMlP93ifMEn%^)CbW=BUc;tnlV2@Jk4G)Y@pGaq+B z*9xl0cEd~I@ROI79u3y|PJp)DG#Oy$$TOhn5V@b7QwZFs?{-tWfv4Qhb|iH|Af_{^ zWp_*8ZK!j(s>g|#!~Uq#usukBv~?yB0b47yR;1h#!}S-SuLo%s8m-hxCXUSj5r&YT zDg6L4dtL~NX3*3`hbcDmBUWx=Bl9t`ToJayD=HGi`KsP#SKMjwcl()oUY(y-muu_% zXyp+EZ&nmJX4Il^i5ah8SYR<9S}~y|6{33S!+_ZgYj~7PO`A2*r(FfHNTcy{(V~i> zmWX-477cSwPOU-g;SKy>OPLs={Ah8JET&Q(3yrZH9tQmk7y{3;*5Y=q7Tr%Jd)G*j zAvn>Rt?xQ6I@e+NB_D}~T>aDE$unk*DGZ3(&IM%r#!8Cqz$~Oa?I}2Btdl^WzlLvS zuiq?IzNWMs2+yFDLGThOc`1Z^d1Qs0%ra=DkR!}e6>!=O=WXZnF0#O*d|r+}#LZV} zwM|Vg&Fe6!8)E!6QBi_3-?%%%p*z{<0hSl#iBKjEE?chs;k%`=;#g$C>!@y{r4&9L zE*!~j@ck?%W-O5|3Y4HoOkzYlC0iG>r_97atH-4xuv(>LFSLkiRLVxG@y_Mb@n@GR z?jPxgbq$Jk;MV$btCbXPN-ljw$ou>p9;kKIP8vCd(nFo`d#jFc+4ah%4>{Lm^`HKP z>dM-oj~GS>FJM=O`Tl^LXcC2-LD?!hkog3QZtv1db9^1-Yy>C}N+0Mv2HUB;R2Z-$ zBgYpmY_u79O1`F$4<70kR|bnFk2=UFLQguDEbA~` zKA%>|-EeE-;;OwBvP!D{s7xO@W;gOf+gE8L%xvT>2ZP(yQF~!3db0M$(f*tb3QZY= z7dLjP*8=HMgG8yO{1dYcKR#CEf3hJf+C5F%B}xwNk4}z0#)?p|;$gzlaTGqI+tZhd z+d#AMZ}J_F#iAX8H|49>hvT3~^kpqh6yfPMBX4vgGNULIH?5quooCcd`bmt;&o_Qo zk3(8)L-1QwBbj`N*9^$WJD@hkNjy<5k{+U1V3ua`o8KUB8Yd7*I@2O%e*dce$-W$h zS2L0ZTAk}AKP65g)}r5zIE4wD=FZ$G9+9ZZm5bjzR45ghZ+)6Z-e0^)-(x|w=>qusOVO`bGqV&u4zV4t-rh% zgN67w&87nD9Nz?q(PGO2-B?oSw{FhdI8f#Gx<9rPafuKC!~;8uXH8<$T_1=E7FX9? z=8-g9&@f*+i|I-Kd3F5Lc`cWD74A?;LtiEJ<2{#l&E#)6N+Ri${kd8#cl(%e#9Wo*+H{ z#py7=rYM>m~6@>H0pJX{Xjj%SgV@FQ=Qubmcgwjaa{;Y>d~@!qu)DlsPvxu;r^ zf0(OH8<{oB5sFb7$B|w@>|nKvmz!Fgm{UDcRcU*4qza20=Q-7L0MydaJ9dnKDs4TRpcvzd^iqR^vUaUE>lT2PW2>s@k<2TaQ z|DG^JQTBEK3vCxLXFCd1c)*aO&H?p>K1sgM!z*|KI# ztmH#-E=*cS(PV)CemaHjt$yXhnW5oiOu`2y#jmb3$w>==9hp|STgyEH8=ZOe>v(pE zo18J<>ta>3{{ql`mdyl!RX@>3#f>Giln%IGY&Ay{iQJJ`Ii6}p9FQtuI`{-Bq0zwN zh|=F1_`?ZZ2EOm*5MC+LTOqaGY4@!ilX{S8a*)!HYb1xC4rC61{b<>2XS7|mbwe3q zpG#R@ifqughn32S5FA=4}5Uch@AtK3l@zSK^t3eIEH^m#b>1gH-sktnaw?JhY6vz~=#Ds{4MR)!fB zkWd*+G*2AQhrC)8t5hO^dk)Q{6j#a&NWEF|l}@9^UoS|u%H%H%9bPZdz1MX%i=n5o zJ~b8O$i3413#bJ1Q(k=2(%7tPM77S2=P6qWPY_N?#Urj``xEIbE}Qp}#ym}5N{!{M zpfGkvlzXzq)0u)qhPxkv54&K5q{tNuGO0J3>MqCrHMVF0RC z2MD$!J>>_H$7lQ0(?A`kA-$-G26G|S)cka;^M>AJqB@+7o_8LXRULGy{5hQ|22LiH zSL`&q8%><+%U~cTkTe-rBXNfZvJo_0l_2?;XP5gV?kbtBC_);_t(1m@BMYbD{>SNh zuQjp5TRE$hMsZMF6tPI`(A=Ydb3Xa#I6`kF)aLCZ^U#STmLS+_{N+rUN#jSu{<&}4 z5@@)fAw4MZluNqqgf!_7^$Y2^#cELV38VK#o04YLSo)Ve?Yrx zN!Okoa~4lF=Q*Og@&W_uHyYI;H3g}?-a?3i>>9G30C0%He^>MO>Y?mYWN_)|#^qC3I zP_Ytx!B|#B-p|sPVeUq zPJchST-59X{ycyC@Y;4&n#|6r8dK0?V5H>jM^EXWH?SXW+I~BB&9}cS?+uPfLfv#p zl47l z?}A!tt+++cn4qwROzpsc2mX^E&T=n(|<{#$FC0p=+D6%zRSm5R-dnbl=!o-^$ z?Z`v$&0pXrubd8AwJYr4wrCg|F8>G+L3Sh=yLPC|cV()X6JyBkBNHyKPN`rUjmPMM zFvBR><3&(0SY2k$<{g0N}FPF|sl|hx+-CG(QEZo!iHDRC{sAtZ6wHJP8 z;qZASwPbDd?eh(KNwW78uXmcummXsiL&7#U`ESdqSy7j~6uVl&#~m51O18#6nKiA$ z&}-c@X=onW^2GEPiIJ*M?EB>6+;U<(I|AB)oR}w*H@n_b-o1ViWNM%OsoOMM z-a=vYmra{OJvRj}Rs&CZvIf7JfpO^y z#T8M`VX6=?tH>g&iDOGviL+GYBB&@e(#`<06<+t4VEO51n^s9I9SLu{CY6APm)- zD|rk*H4gU$#-M*C z>vPvdE?xS`$i-!cppmjx*Tjr_G@N#DR}qvpSXSir!q{D@>wMnFz03`awi7X@6dD7Q z$V8bZj zE2ys4%(LlHYDCvgp6sKk`4j_%da#!a8ORA|O;TN;M8R+WT081K9hAgRx#-66_c82A zi}C&XExnA0A@E+1VBZptd#-SSnH?XUX%zT`U?BWTZ-Y+NN1tn7e(=_#pfpbI`M$-U zu9{d!g?jKysvC(39o?{M?V$;|o^15Z9mzE+aEUC46ZNubi#iO0qS%aM;KtyKE1iv< z)p*5t#dVi7YDY|f_K>TrU0)(wQqq~3Vi*Tk%u~+MvZbB9Y@#s_%lz(U)ncTI)z+E` zM~x$`=@os0;#h+|9H&UH7KL1hSY=z=H2sQGBO!W^u>(n9odmB{G2gR?OX!!gEv0Lw zc={hNzV3EkEbdpbO^u7OPm~W5NMdCdrdCERwh7XHjM^_OA>HZ1CjxnxiyXBZBWx_( zxX`82vAQiZ5g)=8_UO;dm`-T))gRan>|DiK*KN%+{W!wb1mJ^jYM{Td`tDI02+3wW zjn(*b;@Ybt_O-F!3WzXG0?AlnSO{d0+?JOwb;Wio7Uc>K%;ysHbF`k7JPqq1AH9eX zuSD18y0zc4&3kc$)*gPMSKo;aPcgkqc8<&>NEw}o_wXSr$&MGHas+=-IIEzl8=~XE zVMP+`n76sSTy}O~sUvdjp&KbRyScEyoD{I*p&Gc4Ha0~fKL#%c?$nwFCysgtM=9~n z*d)Nn#i%f_o$8cg|B|VO*Neq8a>=e8@Fq!G8XZzuPfZ7kdK zVqML$$5mEt2HvmV#}N=d|A2WtJ@^aISi-asz@_YC*E-@!*SN|~?Eg*VR?f0+x-1lP zZ@XrzNpeGluQTG|P}FQHv|u{<;BI+l;zCc%!DVPvDCdSZaY@v1NE{AosI^CA`!YZP z`MhwSQ-?UC6SEv=Z9-TIzhW;b<|zs`OeQQ%S$mIT(?E6OOYBPM(IjDGhMXkuv$^9k ziPe$z?JPCLVMn`EgDL0^y~##)AWhv&IBiX1xfoQ}44&|dGPvEbZ`R>c8S<%|9b?~_(wGy!9hKx$sn|(y=y!$i?%{h# z(y&&K1AF&Qh9r{qaI=-ctd4T-LsAt8+ChSJWrOst_Rt=bK1aGuPr@>vri%PR!%!^24$vrrnui^iD0!_5js37+o+;0H6)p_ zB06Q-1l!5i$V0wr=9KSQkKQj?ZRhiCuB;+*(~W~Q>@jf(C@_u5MQ<&JHAyRMd3$-V z9spJTSoj+0Zz5NOUPxLoY=GKhmZlWg~#FQc`hd|qmQui0lBE}G%)?f2$vynglVks)n`QBe;MH?@IAIZ}0j#0Ag80m3n z>;N7rfi=U0*juxYT$cWHkq3Q6KyJA^GCj%ICtFBG@$>CR123_{HHue#UGEFQvIV zq4{HVV2G2^Y@mo^&=ShU(2u2(8tWt*2g>{Fvd$@CcSu;9P zu@us0X7W7$n)R{oJlxQopl5qOjO_nE*VrpD1VWQ^OxT{~-M%D5!H!}Vnu$X(E}A$Mcnv zvo*P;>YZLm1QGe4CX7ECw!#h!NA1QvDb3rQ2zPKXENB&{RVk+xI~tCoXzXlI&ug>5 zuY>A?@rn;8cC$Av*R!WhznU7^d6aaJMBqDmP0SqbpIHGJSr4rx^+DT zr9YGa8hNgimY~Bd$T;NJ{FRyTz2#Y*m!ZbT@!bJe)2@i+7jZm~$z|YV)QJ}`Qb&@Q3>OKV zX)6old68HLX9~pE=92qGD5Dt@FHK`TBsL!5KVu2|k&4nvyMcGxg`#cM0 zL29y{tcL4dkKfjDrHqt<;eXna(^rTWMbAZ3Co!0uia6MlvUj$@|`cq1oHPoAH-NLFKcQNMmpPq;|VywMq^RM6d$>5sQDM& zwV0;!t`pj`B1}JFJBbLRgUT*Gi5j>M(hnbZ9DG^qlQ;Y_> zzPOLitOf-AKLNS~Mf*=u85H@YI#HF<$%?X}zvN)#L@F7)HR0fsJ(N~i~h^i>4qMRc+pXt`iRb)`q zQ|cZudu5S8)Qv383Uz)LpFzfR3ukMj1(hS^I}Obsu_kHMAwK7A=<^`D{5J)& z)J9V|LbVD|6&R2<-sm?dAOPwbQcO&SkZ|*-);X-DY89H@oa1fS6*e zCJ7dDk?{x_#zTu$Y95|yg)BBJ0t{#56Jl5N7*=VZ@?VxzsvW47uqDp03 zH%yg8NY&U~M5xV3B94wB9>Rup0ei|%=EKIHRny2A&aKE z%Tal2g;VB?)W^>;;=`;ym4A-qZcr1o_YJWzq8K+3T#oIFA`Z){t$9clkHst$>Y07i zGDic73KIA(DZ1^+6J-1%?u+?Os*kpP3Wi@>$ZFK;_w@Cwy0LPqjP>a4r&Wq50XGeV z?S-A&Yz8h*u-$UDq1mkVlUA3zR_yj=k!jB0v}%^ANkhwS3c{+%GYTzmNm~?VvY8xd zuG>!^#LYHG1rS9!4%;dsimvL5x5*Q9`}9PjqfF*!(al|rT-;fC4n;LxMp=)D<0lej zs+689+nFlTRUBBzyhogukC7`}Nn5qPcD46qia6%mk^-6{Y}lshx+mw4=YKtEHEUHS zY4(}bSJ%n0t<(*6&1|$c3&?9(iz)^t7@ajX|+mCMHD)kJ~N(QSeG{mj2`zuKdXBuf_)tr)js_cEHvz%w0<-dF0`|gLm z=YH7x)akC@sYmLms;=tplDCDoe*g@*Z?fM2FfafB%zFU5ZNh$$m5?x2QB(dVt0471 z8L$L^cY<2dpq|Z>i__h`TxNG|4Kr( zuyi$l7dn5x++5x>e|Og69phO4FYNUnZ1%r!hML5?OwK$0V*S5iv;Pfyx;eSM z%MATT-rULMKX~OG3)$Pb|0l2i(0@Eevvkl@e~)$EFA{(&Kn)-b`1+3jf2IGfPsdyU zfbRwXfQ$X_G&3*&&=d>+5G?(7+UFbq;L|SvpmD;<)W!6FIs^ZHhqbZ-04|FFfRDNW z0QMvRfTI6jj{djqf49Z|OIa!2bx7XJ>G&S30d@dO00lr6-~ccOu)HG<02_cE!1J~Y zka&MQ{_p5NM*i z)qYHEylauX_Y#EnJpUh~fOjRhcP#+YJ69O{AE@H~oMUs;bWeTVxIn~J!=X0A`%E*<6;#{vwgf0fI^T%Q9#fti@%J*fH*52`+Wmd%0 z`@punDd>(`Ux{?c$>r{Xt-Zec&RZKtX2M3hne~<*viG)x}OV8k|e2UXU8`SKf0MOaKWyei}2q|{B z9+Z#^x*b8E9n0-HGq+tmtIb+~rG+Tic4MTH76TJG24NOSbnTuQPvAvr=@^QJO(a4y z1W-Wk`jhwAQ_bbHm15ix@38NZi=@utOhSS@VY?fph4-&FfHKGHnln!=^d)1ysDFYpcEkmVv3$S$-v;B7hn$4fiW)WAVf ziD@n?+P6otU~8V7&ccDHwj;@9Q;Ey#*<4K{;D_ZoIw!K19nGKQuzQUwS5AUir^{)~ z-%R6Fu2Cn?1T$?ha`gh4XK?2 zyc*6xCaZaS?`dOD)Yhl`Q6{G45^tgq^It%xW8QS{9*bq`Uw_;8`?HzQV(>v-2xj3rTgxx-C6z?wuEKaIOU9;)un1}a z7(!@T;GPD@Uumt2&+}c3RZOQHO3m%=WKa2FuKjkkh8qYT^!Kld0kM^W3QU zLgvqn(PNY82(zN6)cgrg?72%UQXkxpC|+dF{M9lB3-xRcOhXBfNZ-8_Rspg(x0$nItxA)J&&mx}t5MqYDuWiPa4uiRbGAXWH z9b~m_d;;lw(YDr_95E8C@%-vGrAVLg`Lq=U5I&Tt;QhUo+i|*VsK8}<(2$vxt1g#~ z%89+#vCj8c6Qab1?_Fn-{~R~xd(6h7+T#uTyul)vIn4E-m_ce;f5+Fkr*Kopa|v;) zmqhR;m5&?ZyB{_C>K7v#P_`c=Il_R)q8fy*zc<<}1^Hw!ysogl=64d7&R_f@@a<7Q z*Fm@{jal7pPw|dT&H(k%4l&SXn@|CFcI`{H{~KT>mUARgo}BT8cUSnA%qUE;84FE| z_$v52vG;n}bZ=Dv(VB#}CuQ$v%tnQdkyUj^1|E*OnFT^}%N>|>*Zu{JeP5!(I2lrq zW$jej!Z&`Z#%k2tbdp5(oTS(Z;{{P;W{xnuR$Y%iaA-15?U8CaN=NQFKkOrC;tmDe zeBI&HOmX!WLA*!Imc$3YYPbjPI|ZmvBlP;?$r?aU?N4j2i0lHFVmwuExUpgs{$+ z28QAOAdAv(`l};Qv6#Z|XsvpGx0T8mM*bK_5n6a)w70m+GjR^N1<6`fme8SMFNt=o zYXv_0dB!|9zLG22c%SBFL^06Ri|8P@a__GI4azPCD*w4G)ryr#{!wD9A2oyHrpoc> zD3wF`+i+qg@{0}_dex;Ytj;|S z(?k&C9gI|%vkyS*fmvt~Pha`FTf>V=s6MlR=QHAU;`z?qq-_}=7RKg0>^D3!l~*f0DiA(1>=&ZnEIgw+QK=^_P^^PPG6vRP;WY77vmQ3vL@D8jg{vCJX!9QC4ah_u!ut z2`cWV&3)1LytSTpSnDdZec@curuUdw%k)W-S9DPQLRbWbjj^dr2sL||=tt7lK!LYJXW4-d(E=hwdKb>b$(qhb)8rF)D z`%)#;ux4}#d#n0Ps9}g#K;p(g$tSgu`4xre!Zz&^ATCVdudd*>8CTh=ecL0+-*ke_ z)I9f=XS|l3$!zWk7*@0k%RCmS=9A5Gz7`5Wz9lnM#R|sjB(9E?o#plz?Rfr@hS0_P zB(Ga-l+Bg74vE=qx6O~IKslgufZpO#?`*~o2K;M1L(O{5AFVrTKm>3OmETHofOUeC z^$hs|^0XNQ^U`mWo-902ZJ@zm_yt|#Nhm_|jeD^z?2!F{YoqP#C#?dy}=(L`ZzO^CTr)Jo)3}-4bJqR zwJ}=)?jP}PBwR?^_db~X^Ns5vm9~UTn;7f!2h&qN(MQF#4`~?YA-NfhtuNo%`^0MC z%*?vi4l1v4-7pEua`KI3H7*|UO>j=8EU(vj%LupMse*R~?|9jHkfFljE*D#k^Xg zsbZm+;1~6bU=#11+QH&o;iBT4#Oi)|CP8nO4t#LQze~tG+S)ifKiC?-(d?1_HqE3y zaoWhC#_wZA5~Oc`b@RojcUv|g!I`!OXKU*TzS5y2PuY;LR^o@gc)@<@sQ*ub>xx95 zE7P)DW@W5M3A*;O4luD>{B68!&-RSF#;o!+ z5N6s)=woJ2 z4;*3D!8lH)tN6>bju-s~;1F*sXd`7Rb6IS^ePAnyvT10B+neiI7~4rN%VcCNiJqs^ zHpll1F8%u<=!;91=%1zm51vmMb-Z%zVaIHBz8I+Na;ovh^O;3u3cR~6R5}yW)<$N$ z(ZOnZXjHt;H=(n^)oYajRe0O_BQ71D99pjQ?$_{ZqyNeg=6iEqW=AK#n^vP%{Yh$% z2CL1~qR|AZgHl0HzLLuFG8UivSVwu>PpUXc9Z`crILL(zW|Pv;Gu;CT=KOXhlhD}M zxn^AEf8t7Axvn8!5$L#T1OXf6;Vk+-#^D!^dg1Bb%C8N)0bX*3ZXBUTy&%KoH$Zu_ zQgG}3PY}W{SI#-|?;ME}Sk?6^<8_t25w)j!{QoO5DF4D)$=pOV3 zpYOUHpa)S~-ljf+@<0|rQ;cgRXgwCG;=gtt#Y?IRj|9?#ffa3ax{qTxb#sBL{dd=S zf?~CL_^7%}`TKoA%9gK3x^?9!d#OI!*Ogee*8>A)R#RRn^$NZ0gxuSiD+*G(P=_nb z&k7s!KewlN&nnPk@+=_1ZYw?Zo>$EeVu6dE>+jvpWE!c7L1XE+elNQ34`+oSwx}M#ZVBMdh zj3UaKAPtr=TJ_bT@$_O#h~DdF2PyTgMxQ|9m)=S_vI|EwK_h`tth8z&E}%uB*|!Dc zOS(T~YKJ`-&WiPDj51%IbepDa#DN*!CjTftfJJLu{R_I8*`&~AtA2_wMVeW_(16M| zo)sVMmnX#AnZotvX?F@OGCbm$Y`l(AMwHU8vylp2r8y;3QwYI`ATaZ{NN)@2eV+Gtd3tyQe4mNHa8G@DrIV4~LmXvsoi{6{ z$FaTu)-jj$*C|M{)w+Vt#HU%a_&b!#@)?KSZwcrL+(R*$7&KI6Yi{Mly-URif~<$} zqFYyMn=#hm*lDViJKYxw@k3hzKchT?6lQ#$MT=G(*)W@qyE;_UM*>&(BaA?=etvCU zZ-BOr+>WF5dWAQ@-ZhQJN2S$D9>J& z}6vAD=Gxk8c3Xz&_8c@Y z?)chg^4oI-?6$DKEX>xGX1xbvXEHm{>&4mJmN0zZ#kPaGM;hg}-Z$?m5pU5%!K|f& z!n$|sDnFo0d}t*A!*y)UffG6SLJ+M>MVPK&us;Ecvx}6Lkbfv~`ONl}`KymCi(uu+ zWT}vA1k-vsl7&^H8OOA3K5m+6+pFz2H3aD#w!x&XcBxMkm)S%LdR90*{#cX*s7fRS zMH5Vh6RKb=t&0RWugiX+5GJVoHy86D>Yo0%k|HDKI7R$hjmNl(;%T4n)Ow0cjCfV6 z@#^df8AtN56b0l)@^0)P{NF4*S))A6u4uyrE@R8GJo1B15lwaRl(QBA<|AY?wdEH@ z*X8ALoLo6#;SZfZ!Sez4m~inFiUiN3mZOlbJ+^R9g80o55>KLG<)O&IsUjD$!3(^h30kF!v6vP; zSLm?+=g$*Kg_jGfE!zq|1+F>R$vh*(J03EVk2w^#Df(JD%cq-c=p zy)URGDZ#19J|~Ej*fks|n71~ygO)x)mk$~ZDngYqh{g*2s;T*rd+JVafHDqAM|PU7 zsiCOac*geV$0hv?q#YNheL(OaMFD*E;wfF4$^%2yYWPy;nlP!GbxMVanW<9zdOM7^x>(&B` z60kVPIX$tlDzYUjcj4mYUA$M^w)v{_7k{2}|XC?c~f-(`QCO9EK zs*#PO@Hv5pMDdZg<4nNPi2Obj&$dw+(tX{HnPOa-@NB*-thhJ7xd{95bE`SQnpvm0 z0s2BRa+*={E%XWY4^>|L-L2PMgcj}W8snK18KE?Knq(csE!f3g)T9a)Qb9}3HN`^rI%%B|KkoU&|+VS}T3uXS$#MIYyO z-unAZ6O81h*nF)o<;q*WURdW?Qa>FHWDzj2jJNdHBu<4b1?`#?&nr61=!S7yVnmsFbAN`xlh)RpJXIpQN^v2p#Ikk*>V#Oab zr|K-S_GQ?yqBVP&HI@-4>9-R6eYwG6>wVW%nRFR?V$=HX$rHV`l))-Fe3r6BLL=-& z`F;MQJZhl&pN1OYV!>(e7;nTV*4?_2_P=>!)|j5TO`#*%@3Hxh*Q9BbVTn)lb021~ ztnziWV~Q>HErAB|sl%(w3*>8tT@eZeq8Lo{Bl%17G3k3nn#aLRf4Qy8Gy7tfub~!w z`e8{Kr-lP9pnQd$+J7UAPK%nQXB~zgl0Zr%o|4gI7bn9>SLoQg`6G{Wr{bEMeAeU2 zOamcXPUD0bfeuyvpH`BepCpt%C3P;zY=cft=8`4ls*+!q4#Ngm@Iqf&P)ahnKGE2e zX~kwGNk}aqEtTDMN(Na*EW1t%b1NDiI-U=?N}kB_Iy*vim&U6s8fWynEq)~gEh2KS z*Ta+h55QRo_ozA{p{7WVlHpHi(LK%Ojvx)t0A12|I);|ic)O`A`y&qhOd7yzA3R2p z{*5i$cJ6o#H7XfdMGzB^)L9>uAAMP#)aVcF{L0~vU_@XQtB8kmxAG&YPEDhX8io2t zse5Mc9v*KEJw7e*hget4+ULU4c@ry$tovl|p z1=1a8ctf+MJ&H3iwl*EecK^|exgn{R#$B}#-F%Xy3$OXGPp7^Nt{KvXjTehgxUaC< z4#O%L%4_C`63Z--)5whInlLpp@Eu$J6BqM7$7eU(i4+cXX)%>ev!i+W$h@Suwv@D91K*-3 zhd*Jt7QC9J?cPlIKZZ-^{Oa;Tw@QpAUsghZnkuQT{W^72pV>|xt?ypV)tpL-v9pn1 zXhxS=o_0D@$S2+aNf~?5;L|8)k2iq*CS~`SYlJnmvrB(7@C)&29v@1M^@e{ z9~AShe9!}yEFD(X*EGLU9a$9#HUx+qqy*u!)7|yU>+7oJUSgQP$DvKpB47fhL8w?W zVrOfQ-C=#Sp-npS_6=|(n))JPXo)j>fm7YoAL5_97r#y$ed}Xni(MUzvD4!?o_c#D|dUoo-+eqXi3X_mUN&?nbMNXE!p$y%g@sS^K`F% z=}=9>8Va7?2wn2Y>v{R}QX(&NCP_twx=}V+Bx&6>*UB{6o|9y1rmues^rB-Mw8H0- zQrnkm*lZ$vKb|r{v@h^aIEC9Q!mE1Bia!)*hw%pg4EVXGXf)Nov)|k1lxihWDbo5I zc(F~cGr~c+f;RuRr@UQpCCMf@w`yyz^q7=M3fv1yAAY%Q%vv~ z@k}CTNMG33+x3Z(yT&r%PH&{KTqxT0;N5IO&d**&nwiyId*C8I4C^mtRB&O3pIHM5^Tbe zPW2Y{>STB(im1$l44RXG|Bcgj9^DJLf;)1W$iLmydOqQ+<_QNfs`i^=>ps{LN z^LjIzR2sJ>`D?zWVmOD;qwoEaxUjbW5gvBUF)q7AFrS`&nm;{Q7Q{AisTABmFx%bR zO5&P{AMN8!MthZr7en7StuHT-=+E4Hu&c-A<}qdI6;`3qb#lT{`p`7`GFwZ&x)ZgW zz9$%GA8IG|RF7R_ZD(%f*haG`6GDoHr#ZL1gi1)@xN?d{U*I1+kUQ2~Tlt+{YgS(o zNXTlpYR;UOwVF%Rb>WGc`Kj^3HsqgkP0CqST0E>XiWdpnDz1sI_Ql}r0WFvI$I_TDK2{og&gknt7Sj6q= zt2mn{Uy$*nOL}BYIZmxDa6w^3QTFhuWTV09OLVH!8}_c9dyZZFP_KLIa~pr(9ZhTV zge|jkhwKYW?+*jJk<@O63vPdCjnt(lZ2;V=BtHs4YD}f4Eg9|MiS!ihNBu&o3|?-- z8ncRJk~9RrhN%T*{f>bDRtSmG?1D5^DSc{eM9wNp0+?@y?MSmAJHtZS&e@jBV|+V2bl;i$8SOODJ#Ai2??WHI zqlF2fW@MVw8mSF3BNf&tslHnuBJ!+@$7;eFcm;h4Iu)OYzn$uCbzqC8DUn8M=v@Ws zQMPf!#q_Fdvq5lEZEyO>wSv~UAHiJ09-QREjsD+}WQC&)1w6Tj}Jkfy!F1!`4i_ zlvYAk(Q8fRB79*@<`G@D2Wx&p70*SZH;x)z(~+g?7%)+P{C$lDY7u20589upclZT~ z3>*o4E?%+@FF#df7u02D(F}~hb}0R6m;v!8W?~D zys56yy#|qo^RBJ9k{2?L4lHclpv2aZlH04r|LI%YR!L$4Wg_vVCHE!?k9Q zjO?v2bDSKhS1kOEMLK>dh#>bh?u4SD7G>q82au0}a*QjJTl2E+Gmv$0|ch&1}lut%=*X z#|1&9M7%7PR9NU?A(QFWQ&%Iml895`kk^9H+iA~+(K>WjM`xUn>t9QcEz@!`#4WSv zT7NLl8!C$Bh_FDz!Y*hvN3L$p_fMwSO76*Icd(F{#L4i2ADv|ltBy5=<}>zn?G%dD z%`N&zrblj#Q!He>+;XG!V!r%YRraPt7`(9x!GjoV2i>elg&qjD|1dLYrow|l-hhvD zC>m*x#AAkX#G36mdL?^iV-vyHf@U?#?rThgHqBps;d!K9WH2dCczC4m0q(P0DawzA zF2}XU>3d`Jv||1g3?0LYxcWkwkWo3oVu6R7U~H$`a`yZek59lf7IME(pw9O5{n%$a z1I-I?dw(u%ob07%U4tf8azh?(>Vt@S!EF{fqvlv@z}50m&P66Endnedx|{}9n_EK} z_A#8Mk(~xHAyxfP_|EPI*%O`6a&#&|?=fg^AZ%f2%XjnAx-p1+y*Mvr!@;aHr#3d` zh035BkPZmGmWjHV&AxtbsDm@*S9YiI<||yO0Pg-eerZOBF-!bJdfyOWzAp3)Aoo$r z+EJV70ct=IWwngg8=k;L-tsk=oajQpy5BaJo-Pu4p{WRHxC~ab=bBR3EiElfGqV*l z^vFj3M}C6yuet%}S#^Z|kmbR-v*<7Kh?uh~Ye{e&w3 z2YW=bcT-zY=CVOeDdHb=OjDZKty^AEQ%zl7=$3vD6i=I5D!WS+@WnhEKXKqN@Z;=? zHFhAmJXe6`&_XYVB!*h47NsAWs<#Jv=`L-BRKvw3ZzSi0V zrUvo%+ZgE0W8NnSQ>8@Q4C?t>qVt#SP*moQ7ym4&mYA^L=_SXngOZiwMPD|v2+yC% zWaMnBTO&dzr&-1S{v6LOCETMPe*+W-=w(IYvukp4B8(cDInE2(I2=@|ya9@p2pGsf zeIo3xBINd|1{v0c9g2e!hhhmNu}8r3w2Rob!3ST=O^KNwFc5J#tUWF(bFRzQ)?mmoOPCM`JHGc6T$UU6Fbgl{ zS4B_xRGpV@+Bq_=)2~jxUU^y5g>YAM_5EwfESE``8^26ToR#;N?g4jksZ9o4;8oju zo#~`TNGjUnHbv$7>QT1`hQ-Xw@c}FvIDKRqq0lb*{u>Ck`W=f{TrQ3HZWl zKCz$z7j!!*q3D#Y6m%^;Gi6xtvO4$qN2!V(Glvc)h`0XrdszqylQHI_F{z*}%KJj- zT}x5Ak!5jRr4t1gFN57PykS3ngI=f>pFTn7xCF~uJ-xF@NW)>!-y|c$plTExrs2%i zYr5Nk4Ufsgw1z$T1vQGFA0j{7z8#WuZ-Ywtgy6o}vBer$bYRlySKi*1 z;MY7{nhJ!|J`3PQzT*$S)Fqg(49F^3 zTmL^)ZF2e?>x);@x^D?1dKxo|8Oyw&CV;{suC|Ez9WhYdf zLmc*BuBXO`O?dg9;a^2WtDz3LVKV_qkF&j($9?LEC5;EjG6AzWbKyr_5_x%Ib>vD~ zbSlG;xg8P>a5|TF`Ke`O5Eb-gkQ#uujex7U#w=0OJj{Ii=Qw|!ctbqRB8`mMSy7|A z;VSjP$F2SN(bm>c2_pl1`8m2Su6`}((34HT4@9xmQF)&*3m2K#F;o{k?XD^Aqg9Vg zj5WHtQHfD5klUwcWh)?6>Qx`$7`A*w`m|p0cx1W}#g(|i%qBf4B0orEB z)#NkRcUANTa0Z+FrrfoHa=Hu)*^_QE9|jq>$fy}~ndD`$z$iV5)fL=cTcVDgGjh^5 z1{ysQG!D}-T-RY8XrZy*o+#_eV2)YfFg>yE9lW}cwhe~*n}wzpCDkXYsc7J_f^xv( z*C`B!HRZ?37>(Boq1`3QLwg`bYT5_ zcv?0KpLqU-RL+ar-e`&nIn|*5xenT{$8F&&tVuqru}cpQ_Rv`$XYLFYiQjd*z%*1! zZ5dqN9JUfnMcUd<{=nn9P+LA8(O5ikn%xeMO>2cT)3bHQVUs3B%rLTdcD%$T#VL7( z%#I~|qkn}cpq^4vGm?XDu3umzs~eGDUD$A z+B%W1v|rah{5iS%o$g;9+C2G@E@cbpnAIRl2p(TCIRCNk9Npj;7T%>x z?Si&wh#vzU)8pF{XYOd0O9cJ7!bQv80LU|&nQ%J-Ec)Dqt}Q(zW@LjckOZ$s;?FM) z&HQq*5hK7*DK*5GpDI0d2WvfP{#y2Zr@Jy8^wx9GmiJ9Ud!hN2#aOm7AyJ{Gh)*tA!Awbx6^VQs;2gz`>NN<9MKOTY&v@V9LcIi4n%7 z{7jAAr;()%TxY7n)YOtftCK?KLevYCunQJVf5Z}}rzRVtK(o3YkYZD zbj)G;5$9J;UlliE&v-_TZNB;{*FIS$D)ae8YB>MPUD&a^4?KvA= z=cS!~Y@Z22^6R{Mf2W#JqG^%Z`sccxeew&UPndIbcmA#}YNAw8sM=+1AE@)S-#@2n zo|0ldhE;3=mnH|D28-!>c~o17XbA}kR#azcDu+03PqYD?yTiQdcARejTa{5Mpxvi} z3v_gb`3LI@VK6?r#W8BU)xOrQCW+!&!lAll4PBeG7QTLE#Vk!$0(U;?M135O49B07 z8w`ZZO_4`vUo4sVzM9|UI?$=kO5<#or5)GDE)b$xajuznww}Ev8cOky_bNqZx_D>u z3j9c9^rh`G*moV<>*ZWax%COI70{~Lz{Npw<=og209p45kU6)w*Fjy3nG zMDwrHwrlWJ1sWEp$`^l!w_d2F{=vQM9LbPHT7|9&U~-erqUO^JVVu`xS^WVHi^dr zt47d(UOB&qo2#ynOqa5HK{#nTIvbAJ95rE~Xw@(ao#-oI);cg;tA;xwd4oLV&acG( zv@3M-J`yLRRE>WT5wED_4e+37@p6RH&KfCI$`B1z3-vf?bRTj2&@9ysm)hA(ioAtw zbP;gVuIJY_7!x_)bl>C+ju`}I&l_y=6dd_>44%y!5Oin`K4;I)9vB6d^}Exj3@~Pt zAjWpeW4|L)mzfc5uPA`ko$D(_^P7;9mRFDF`v<>Z7JaY7kIgvKwU&xH4sI zuycpVa}@rIsNTs37|f@x7u|YRr0D$Ds!OICI!^A|Uj49E(f^7@&lw@@bh*F8Yb<K`ytyCWtt|OFNNmn%wQH)SZ^1kYp^Lv|G34}!BQ&(E$wVbUX8M4K#}C7q?@NVcUv%5y>aUw**XGU6zt!?*p$f$mq>o5f$)#1<&|iQe@%dUYv_p2`b^l|oX2$L7W6j2#I(Gdd z1gqTTLl#ZufZ*Oa=-uKg!`gkhYLhqHCXv^$bcy|C=1vO1ssK3Hhe5swXe6e#SL z{zB?`_245|~Dw zcaZqIlH2$h`wz87o42EsG8Qv3R7Q-^Na0pX_Zn(kz~Ws=OonBGnu{2^rnNvYe9@h> zbPyqGI%)(q7JA&zTHuDFamd)cr6vydA7eVs;R$jWVy_}WlpkaH+b5~^l+R3mxHN&bbNf7dcJDQ^)-7%m6GkZs;Y z(jWG-pf=tB-s83kya5Cc>DUgd82WZSw%hl7(=T78h5%W%e64hUxBLvzB`V5Ugo6j0 z(q)}0xgj3;Y+4JVu^&R$F0_>!FE6>K)fFkGab&E)LHbROYZSBoBxGMvz^{deZD%li zfR9nOA|5Vl@yo><>4cvnuS&ojKosZVQNAGL=MAsDW+tFizzD(U3(S2G5ZmLvm6u8p}^tkw)jTPiaB&6RJE!n)EGN}4B z$gPIc@HKYG%r7!+ySW$Hvn(;(T8wtpk3l@=Hu#FjpiMU z)n+bDUVo69y(7#-i0@rPZf0Kll65=6K%Tc+Nwg2tOUrO& z=;fCJs|g=ut;tluXAWxZDhgC2!pt08m-A!&LLL`VJi2Kp}S zFurQh!b6RTDQNWJBoCHzCjoM4JV;@;D{;u}+m@IA^|k+_N^ACzCU$E1Yp=LYK{*;Q z$qfrw@(n&aq*CbR_KkoD2dQK z`+ycIT1V1Isxiw6O0sWolruNqmW%dxb&B}1?o0x|+Q)#& znFPzd+uD?sfUwJhdeg^Fyl=gCKrSltg};C(@|VbF27H_DmCKlQr$=HxD8J*M`x~IN zxPJ|VNm;bjvO!|w&=!Gnusv+vJ$o)35kU&Y4RYiN-8r2Nq7 zema*Gmg0d*V}F%h(Xs*??m{D!o&Vi8p)T>c)z2zQNHMplZ=!t$o6o0EePE^Q*?x|o zh(HKJg5gt|ezlW3@p|}M>QufN5Ws$%rg#!)oU}>(pq$hoWJHCM8FP?A z+5oZ6e@H$ncEsN#Mq2MT{Qh%(x!F0-U9n_=r2eQl?JrYCzhSMUm+Lc#+Yu>KkWO6gvN<8`O44qc zlVsjrA5lEx$GW&%Pn;FYHx;)RRkZ1inE zYVAnZB=2a>QkPT0F>jLVJ>PJ_u%u$wT^AAZQN68b`5*cm(u(M+KZj=hx_ixhJ|QO2 zL%uE~i+pM10cXxF&M^)EpILCGNuaWx@rPE07v8_Es0gE2PQmqpmrlI7zJ20AWsBST z2mI0Ju?p<_i!;()E9;B(8~i4963%2a$Qjb}qP_P)ruED3`b|TyZJ~DOy7ulK(>#0% z4=))9BS~^+K_9~hAaw&s9KKqhzwBoByicl&=ISE+HY~JW6Dk?RZh0AYmTv!icofO5 zj;(;t3bMaCv0K;rO-snBtA(gAt4G&)>>m_zD$ti~#{=)BBibY*<)gCc{#$97+59(_ z)>pwKg}hHJ@L>ajdbh?r@|w=745rG#6LG>G!;cKZD_ajsAA?tYnlh|VL0}$oY&(3(n471WwIHACDVM^GvY19&8u?0|y*vha9(jq8h~uu}f0$@Md$wh%nv{o(Y+CoauITyfFDRo@5OPu^&J_P9MqDLn)uj*50n>-EG$ja}{Q>P)a_5RFS zfF__y#;VcG>;zm-^}YezcY!&(1;s&Gn!nTHyE!!YBx6K#{1AIU=UvttPoHph<@oHC zIko$7W^GG9#(mrVTSFff@}hVWoVZRpUuWShhtC(uG^R~5M^&TtwMnP5Ys{Wq$0AFW z9os8ha$L3&Y`VJm%3bokddRUV=;4JIvokU6n=t_)*j|@g$ggcIQBR?t)_2hHlXbxA z2_`1#oVO!8`IbBc>fQJT2plLIm!EgwD5Txv*&(`>=H>eAD|xIFCsNbRYWo4AYGYX| zWv@9`1WLQWeEFmCE3Y{K|8+|fiAhrqeq%?GrrRIoF5#-hF1~HT-XOL;|??yJM&(~QG@QiCrZ4coJMkM3!pBZ2eeiS}c2U;P6 zUGF01+ND+@QMN;_&e{&+-g{QKh5LYUm0G%k)dQ+_e?5?p!OJ`b7beKW zk?{FfeezziWCz;j$a0@0zUhbAc<%svOk?s1pH5O-L@r)&e$_hsjzVj*Zg1{f;l`Lp zEb?fo&)@;_*>TUQPX-oI8QSvgyDT{aJ%M_Q0`XTdX`qv|vdna^a!HiBmmaOIm9S3% z+u1Wos)}q4M*p~$me~_mGnTdsK&tBFj$YCr>sArP8kP=vMOnqq-%I|5q*Zt0Rn?bP ze(FY68;sO4UnD)^ju9QaESK~&?y2L}X}^SwwZ3ep3&e}$Gl_gg09E;&lP|K%D%ts{dBozCAeh- z;j2H}S#mXL9otBLIS81G7gEje`LMJ%vVvE(gvXNQ(8}Pj$C&CC6jxN^dPHKC%p00m z=Fl63+Ra(WHneyGZpUc!E9~1wRVrlh?9@gbZdMe=lru5 zMlv(`x4ixpT~yO4JUXi7c~gkf#7c@$2Lm~bvvOC0o604p z(*4`;x}Fl_cXyemORdS6*Ej^T`_}{4x649cwk@4@4&U1_% zuZNqja{Z2p@t#D_OLJ(G7=1?Od|#+~G12Zp)=iz;-;b-sIqy`%g~)KY2`7>v%PrC@ z%}@O{Ya<_oF1!0a2eXF;s#BFBX|(3p$HgV?L@lkOrnigW{H0>Fj8c_6t3T4Yx=o;v zw1_|DA6Giv&v_(qj<&tb>9dgY{D{~b;7uX3pS{S^%+A!+E?e07kLTAx3@qd`7GaHO z4iJ!@aG<1^iER#M?T_=jeQ{rS_?4VWye&CWrlGvU@e46~Hn{+gK;zhY z8U_iNl3&IKijpks9z5DL{yW`?5(%&r?yaTOm zV=S&uCj)wS0X=GMCuP?-M*|3@Ei|q{i$rUB>kGF+0Kx_CGt~1y%*({^=1VYxfX$(W z?QZumsp;azR*?1cw%z{#wuu~GsABSP=BzGIG!38Xy4mj5bEfvJO-vXy*8$vM!M&FX z=PjThk9VJW1c^~>~ zHU+XvJUrbkmJEE%vb=^;8Tl+K>7LkOdCpE{wG*qZE%zG&_mYmGrn*$+WSkK!<(x_8 z!sz-dD%v6(mwMg~n@#3tQR(6iw=H6;yqrlq1_9C-12$gMVi+Q^kSt8xwk3&SJDL$x z^{Ce*_q8fdDW5qT$KCZHio%hfsS^1%*%stF)T@9d1`ITQ&|l6;D4J)Qq85dt>khgx zXLCwpEkN0Txt=oKg>6x>Td$duwc>o$4_hfSm6P~;8M>+8WuK8a$ENgmex>bGc4xv!Lj-9;g&0ch2UPXw+~j=_ZRvZ=cwBq7<`+ z=?pXX*7=BIUXnAm1ar3LaRq|q<5ZVYzD0E5Pq?`Ex49m#?HS=rTsjto6NI6fh!or8 zEzVl$GR4>yfmK<@jR7$i5T%<%EvWGNDF&!!k~&S6#>+mI(|-}+{{Y-MYH38Es3j8+ z?Q1)1<3mp|>gQbgi^jC{xaJl3uMxwggRQB;@ri)EK7%T_p!pX!ZfJHjee2Cs$4^N^ zuzTGAWeJBD8GT0&hrx54Y!f6IyK{G}+g25OKaF^|4e@SD{{RbzNpgaU0+W`Iz}4{J zWC^Jzop%M%P6yDwj`UB6d@D~MBdb|o89H^r8tYzwNS3xAAM0-*mwK(ao^!>d?)3@% z-F{un#E2RxxKAGk8Oq*pw%;u(XCp&PRi~}u*6V#z9yvu7(?w||T#&;H@cGN#z4MJO zKajYh>yKZ)XK~(ubf=TClhd6po$+3kZ}fwwxvK4Sx8?6c?TN*-Ic{EV5;^a)(%sHO z#u-PbhO)=y0Z%8L@|>Gw9kWSCsj ziL#}xLxJ2rNQ2=88~SfCjck1;ky5%tTD)V$_!WCKb@Ni0i4dq_s!(aAis^)E3nLfT zRN#AG506DP$TghMvz8)B-H$mU(W!?U>BcsA>8w>(e_6*CM+Gt`1tHpGunz%W-ARwK za<5pQRK;saymzf<^e3_$_3*)^__8uLdP^~P_tBNJ!>#`S#ezg#&AMKGm(eTF&(dy| z{CZ0xpX{ak8~l_R+pHqs9Dk^s=NFK&%A8vmT9jdT#^+G+Lf*zvlhnhjTx&jF$e%z0}rmYHu9FW!(!F-d|p`3zw#y9_#PN-b>c+ znY{CJi(>RMe}HhFakbL^Pst-oo7V@Y+{1}uZSobg^^nrm$>bb|qRVgc-kavL$uYL~ zG+5*OE0gi8H=~Z)ZmHnQTPZF(%ku7CEvbAP$13oSA1!sY%`L00O@L=3CP|yy8stah zA6oer$gaP2ORW5jqc@|}o@uqXwOG8EnS5|&`dmYg1m>(YZ0<9~RLQ`~3GHjIotU_P z7K?cw?A#Qlr9$(IgFL5K7rSG>AYj&(fq9?RO{yYBwi#j+b4*bg5{H{)yEq~f>j%)s{YN&?F zPs4rpz02=rd+RiW62g)e{{ZbM$r0K=oY%{|cklR_?;n`0yY~F{{7>q~P5e`d`CMl^ z`G-ea<#a0pp+rvfnD1J|{$gS~_K5G=CMW)X#82iY^Z8m%Y3m@7Ib-Ga#5x}+zZ61E z`{!0y4C;R?;cqXc#d!v&AAM3%!8$D#M@rId<|t93Fr8{>>^qVd@~C9girPySq#oVj zv{e-WO-Yc9-tb*)W_P{j!k=t_$beh?Pt#0wZCoGTq^7XzdTmP!**B6X zjoL7ean!9TTblA}S`x2>d2?DQsOB1Bfre);EbVBX_cdov6mwD9(EgI`pG|5yfz~YE zl5ov#OSzU~=~mXjTTVahFX=^7A#O_K((+g~IT%kv+DB2~J3b$=R8fTjx>nI9}cH zM-Pd4iK4Gqwar!qW`+JDu!u?j0Jb5NX7Y%Mn|WMznWt!&?cein&u;yv^$mZ6#%kr_ zQP`SG5P!&tfy3Jqs-O1H6BfEk>x#L+mI;tZV-bJvq9>)Ci31Ac2Z^WqHlHr#oRgMkak_FoOUa$Ki@JP>_tiC4Gwz2oEAB8H47|lmaDIp3cxlGs zh?NvpVWrEPx5}vcPPP^M=i>BuBjA;A>7tU76NkxHJrz7fs<4-trjd|j_>^Gm)amk0 ziF}3X*8t!RWUJ6l7t}QR#q!&zJmxesHMe<3S$c1)mD28>^qK6~tJ#;X9}eM02>B9p zUeoG7E7|5Z<(XSN1F39v3_Vljy+)2a2YAV~ zb`#_MFXam2@D&?pG%9kV^40lUYs`FMa58P)B{H&hODQJ3QTv%~S(;ng(V zqN<>3k0DbpYTqMYGspM96lTJH>Es>q#2zY<$NO@{#D0$5jS{7hb`&@9FA0SoPTINHso5k zk8!Fwq?Q(gJZEhU756|Ei=?5VqIthFO)*X4WLY1CA*omb>P7Q@HQ_bzX{M@{m_RvL zh9z7DIcjZ5JAm>ZggXo+Eu*~)VZ|bCF+GRp{J%e*(>DBe{C^+SjXVaXq#A!MxqDtQ zzI(Xqi>&2H$kl$oIo9F!8zRJ&B7W3%@SgGfzccym+q8JckfRFd-IOZxTu zG%pY-L^AwQl!_n7|x`&j%$$9X%3H?rez zJuwD5Pv4|%sbHoeA|kO7Uf+m_i2nee_=x;={S{3-oV6wI_u35(GqtvQ`faa!7t$Nv zE8b)O0B3&F+GD?$e$zdtymtJ5`TYKw?Nzl=cqDw(`EA>6d0<|I>876{1{wRr1xUo+ z@oV0ItWWsuGXe-^drx@&RHhm3JrzcRUx zia%b0UVeAcSJ8f^d{*Jv6j!bP02zhW4p7nD{mZF5lsqS?Hd``|47k}DyeFP#XXf#& zymcu-xJ(@;^fxRkxa?ZVqn3VU;{O2YZ;4gR_;u*CF}EbF3sukYW6 zcx??nA?zhj^wiYVFm~?A5qr4N>$~$a`gw9eaV`ilZi`JM6Bbz6Ds@)r7fcX_^dAj%)_=+Z6A`fs&3^Wa%X+SPq}SIIVPhtj~2zmDan##ogb~*U!Z$xS|X! zl=4IIGrh*~1#No960NSXms*x&=O6duSKQPFaIa^MF50iZ+>;C}C?sdD??xpg6D5q072QJ+M z962U#P8Y&@c)IKEan!h@%bR0hNapBi@;s!t_PDe*Zn9$7)7xW1VVi!f{h!=V9i$hB zR>X&Y{9@wtx)-{fhsM3{@b49Bs(HCiQH%1@0De*dDzdq$oAT?Yj*0&OP|t+_02(?y z)1HHLtET?|A#`|WOn7cD6wfN0Kc+l*+m9rhI$WQY>^Za5D;M`}pmQeqH!|Mz=fz8@ zy1?Q_1=pplX!jGut3tYKMkPtRfhFzo#g}6xK&-B=k1pw|z(?6D!B~a!OSD`2f$Ywc+GFW`_-s@;gg70E{+Ro9R6Np2 z{{RqyGY)LHduvPKUi|wH2q&5;guPgc5#JGOcaupI`hfDV{O{N4J<-YA>53gLL!UvH zX`yulyn(J{{{S6k=d7zvEGz*eryWf=UJ0@HOLMpGSN_Dm!~X!&PA9O}4OOzN$|Bxftv~IK(?6clQSJ>xHHBN5Fx|!{_r8U% z-db%HE&l-G&GBTI&JKr7W)I=V3dm8t9UT6AMTJ0G@1Ip%;eRG5wIbC=93H^*{{Yel4vMQ_3RHPa(rm}0ovkaRr`}o@m1mg`%TWVu=}#y1od~^h;y649 znx%!u0?5!O6v)#wy6JQpE6nKbE^=Juigpx*C8{9BI}gVF?|&%q=l&r@JPPUp*B7c( zi_>f6>pIcW^HN?~xoVq1AjA{Va*UQPv?-rqhwd+DX#W6p@BZ%rR%h9HuXysB%-8Yd zSf2j?=0DHsg1#Y9BLy=t@)d+48PD?d%tfQCn23+Nce9oV;?$q`k2`)lfBKmHcWSFf z_qSg?Vs+ra;=#E4{{X%_ckTW^$8X1Y@7ia-`H%emoZ;J2QOlH=M>)^meN`OSg=KSo zJEg~UBf^b_-%;9DZpL1K=gCbaYiq#lW$oo#R`LD75W%4X1-1VGSWkF-D}~~8R&f%Z zS}Q*(dvwP5h<7!4?7k<);n&0Eno4WouP?%>QPK^(@b%Y0Ukd(R?fkOo3&7^N)>Ejx zJak

    R!0Oi+Eg@lyS_lr>)8HLdScZ4rP+U+8U|9otRwxmo-#U7|*JQ{d|G%mxKF1 zuz00>IxbaJVh1SMm$mz=m7RTk?pKLd;$9O^;!}DlqLr!^d1SF0Gxwu|uDdO~_M6+x(?-w{;jZtc{ z?X=(N*M#t&Y2j7Pf5f5rSBNu>+rE*L{{T@cYT;MU@kb7?{{WhN<8AiII;@`f@4{2$ z+<+w5eDf3Nwoo`6W%Fm_|0S2!QpV};c$*hCY~1z zo?2$qo5bf|DA!eV)m=Q*L0xtfKQpncXL=*k%Xs1{@_-1UvF>y zzIUGw%-6S-yOqPDi~JPr{U?6fVy;#5S5R$UobY8>fRBr0PlQkMxZ5w6e%+(RhY#Zs z{{S8{ALe`a{C2eAaOg^=ns#kXJ!&$Gw_PN=ixIk zJ-bJ5^FNOB{(sNo^zx1!OdL8$zvUUhxgSSSky9L@ySm`q{b%QWYYD_qijdxvzY$29 z)ew*IN=1J&NQk$1kNwd-zvKMO{=Hokl+{&Kj1vPMdqX|QHq?O=5qP#Et@rKn)@V4T zidqe0gnr1Gyxz8s-J(2uMJqmE=H2_vdwx5AneCr^R)K%^@8?oX`ynnbr{UUZ9lT_n`_KD(eo*&QD6O>xW5tOUpXb$#%Lzel5cz3N+Kf}##ytxQ3CjX{8mPrMY>RVV!*6;x9{iB+Wuk6yYC zx&oxKfbJ*4~72#lTerbl9b=RJ8S>hBS?v% diff --git a/PC2/wwwroot/images/about/au4.jpg b/PC2/wwwroot/images/about/au4.jpg deleted file mode 100644 index 1cb6f989659f519d635dbbc452cef259f29f37e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8400 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 ak8O=??F`wCDCiw39=_5a+|m}go&OKa5pyE| diff --git a/PC2/wwwroot/images/about/au5.png b/PC2/wwwroot/images/about/au5.png deleted file mode 100644 index 306578eada0bdf8b4bdfd39560dbbc7a7bb612e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23369 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 diff --git a/PC2/wwwroot/images/about/au7.jpg b/PC2/wwwroot/images/about/au7.jpg deleted file mode 100644 index 47cd99b52529834e94c3dbf89511f5c0b3682d3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10668 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 I{_e;B0ni3vZ2$lO diff --git a/PC2/wwwroot/images/about/au8.jpg b/PC2/wwwroot/images/about/au8.jpg deleted file mode 100644 index 21f1e6c7a740c410fa209ea49f8866952b85af7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31348 zcmeIbWmFu|*5}2ol^0gg~(15Zv7%KybIlz3~Pb zn9jZTxp!vPe0X?2%v$dnPIoPK9sZ}P_OEtzoj!-!+)dvt1MpwVDarwmkdOchh!@~) zg)U9j)7k<6P*w)80ssIE02%-rfQ+!7Ay!O~2mvSv8wv3OAo2XWJ_L#PA2u;UQxa?q zK>ddXfYH7~2|!2G;ULzv0006x${mD-fmj~O1Azwu4+I_vJP>#w@c$$MZEJfo2M-t8 zw^k0W4z#jn)~*iDv^?CL+?+hz+&ny-Jc4|}{DA+SikF+0Pn26&l$W2DTSSzPTadI`LA9H0cAAmzisZ=n19$P+z3%@?0@VZNdM{G{HN!FiU0TXJoH@tpA6CmrymGB z5O^T)K;VJEzX|-Wy$xPOheHtXf2uVZd@22nO0MY;qG&FRyM;Pen=$M!oSUALw zaj>y*$Owt>h^feFsHw;)DQOuwnQ7_S87L{A3$d_s^YHWY(=dxj2=j__^6~Tj-3SsU zCMFIx4(a2^q`Y*LbiDU2cdY;d41g!V0tJZSr z`s><1Jp1oC_UZqVXa9EWKYq;ua8QsCCl7@HAO+ak1b&_L>xDGBds}6mZ7zz0S!AGg zz~a!Mg}Fz>H6pgB0v5AFK0%TOw$nR+&Ew)hE)&?FlAPHA)E@SCD=x#7nxJ7ko5YyP zG2K2&P~SZFi?bdzO=2#q@Gol7@D9qUJWmTXHz-IW@Eh9XTCc>X_&B&tPC0Q^-F%;L zOw3iVVe5^YO+!CM3E6=+z8+TW;}&Vz_ZNcvoXtl?+m~oHmZ70|1`d%Nv-)BlngeXB zQQq}nLh|{r4oM6uZEz~%=e2`+b-mw@f8}=+*kxVaTK935@4Z@^J z`b^oqId`2qmt*W{DmU)xN6|%1Gr=*-LeAZbvGP$HHDKO1^9%ghxQ&aXc3o*MkRpOe z^_$3GJ(%mZN>{gB(!Q!q!`+9tDwI*Km!XLyKuB(I6Rmnp2dduMK@$mC!O;*=wRQ zP6960Bw(VG8aGu%@_S~~b!x}oq1d#~(Dw3F^SN2_TVy3}QmP$0BhS+ArCuF||dJV1{r1FWP%anRcYGzlrKW~z_6U2kiQ+=ks!fI4O$};ox z5D`r6o)=3uPQ$AjUm~{W$GFlt^gXyqn)TTFB~HDSugom!F5`n3nw<=fCEcH?)Od!y zr`7n(SivpflnyQ7l9sQZwC7yA187C(t4%*y!|T=fxsNtbwHA{j74oDvb#7jSY(*Pj zTCEDxB!c)Ds)Cww2Fo4?@^agzcc7mZ)8Toftk=yNREyy~w(-yl|Fp;++kvHOGukjo zq0BpHAawZj1K+kXxdFu9-u~cVRx46KdMY*LwBgC)<*gT6fMK#tN2$>gbWS?ngQPrC z$lIQ1US7W*7^KKEa#TX~wHE`~{0~Aj@Pbs3O?D|-xV2Jjre%CYUxGa;B{^G{BaM^A#Y=Vp2Pu*mQTR=h~E4P&w*7Q%pdMaH@%Gy^m&NUAx` zw^AbqLv>7pjp5CfPO~Lam>iNIO3hS%+M48!^eTBc>-1)t*Y0Q&xP8>GrhQSZKYlrX zM`~E0z{x>6F*Av)k!M`arHS~5h}LkaR7_i;H*S728Hf4x2v7JYf9UBQKr;TficqH@ zNA51)GbsfD^Fd|4ar@P|T5YNlf?um+BnuKz$P{TH$degytK#16 z{h>#$-Z}&`uSCu4dw!~bi1{4Fnak9Vnr?F zi>H7?k$cE%|OZMjk({9ho zGJlOapp$(9HsYD8q34sV98|cx9Yftd4|6w?N1+tH1CS5@Di1oBkHt5sdxL4Zy3r$j zRuVja636SSiT*ve{pB+&rsDyEAq?g#H6uH3k8hTB0r@tHe2vZh2NKuX-$6CN=2&k3 z-DR4dxXX>|{IU&=`IV~Lt`7DifF`k-DJ4=Wq=ud8&v!gE7r`4e1FdR-tbV>F80`1j^6=-f$fc1+R^gj8T8`ma|BK!ztZ|saWkKfYj0(q^8|nG~C^y;T{rTn%ARK5uxG5^rwma zS>B@M7i4|;ww2$>R|X99?Y^crM!rq&a#Evl`>vLxQ8apL#=~ZPHE~m!--_F=2@jze z%AVqHdIfUTJe33`_^7GvAEHEvc9T1$gRjx_spEaSi;xx+bhFPNAPFU^kQk`OD$*{MR_yB z3;a}Ek&8n^o+x$|GSw7IB?s3*kPpZ2BU!3nL+3TR7$T=zjz|Jyo-#d7s2X$PV?`00 zN-n&p*000y;{E>ceURAE`x6&^lubX^r5%EfBYk>Cmf5H`a1nlGgL|3@52b!KIg~f@ zBE&>N=ZRRkx!60iTLW!ApQKt(Y*X~L3x+$ugi&giMHCuuy^vke>iFrFK^dWt^v%>A zVD}CXVeI*NepZJ#zCfVha#O_$JfV|8j zgUHi@9GA=h8$p5hM3%`LY!v8h7A>=R5hP(4h0^&AwM*pBFv9g%+JO-U=la+d!%6 zlI0)c9B;Lnc3hxhJ-T|-%ts+EIPen;1FZxb09kco9j~5r%vJ?dw&`9Tc1mYKF zreuA7rUm6~QFHlYsE^ba?DPEuXN?&nD z$jn&uQfKGKxIHx0OA?`=$~om=m8wR0jM@~LVfOh78h>s+-NQ2NvWdQI>nZL`gV`R| zj7yKwVv;>}re{g~ZsK=}QF=K4B^GI7Z;U8}&%c5VI)EcNHFbKFQ z+S#v%+nZ_kP|2}njh?Y^F<5hCUUodPTWVJn78q84qAipw)<7lnnO`iV%A>gf$SZWq zD|*s%TvFOaj@6+5&GY;+;>5Jcd2k^uLLz1mmNw)A!q|`LN2AQ6T!pg@sj~&ue`b6m z9gF#nBjB`{MKZ{3Vq$1lF)4#dh>TTj3-0HF*t(XJz-bUO^6Qv5OV676SMTU|zo?){#IKBhakuF=#j~E4fvZTA4LHvCN&zhC1tD`6vm%R(8v6+L3 zIj5F{-r>yNzni6SUfyDI6e3{9h@z>ctk`*xVU+_czHPx3Jw=9dskym4tp1de{Dd< z+{M({+R@e8!JhW-28~S|+*~E--Q29rL@kUh1We4#1UO8L`OG+Yc+8ABjLrCXIL!F? z%!CAZgn2D^Oz8jX0PP(Az5D+o3dH^pE{RGzn;Ro0mfkry*h>Dl=90v@{`Y48*^ar4 zv8%b{-=Q83Zeb2y{&$GUrhg3o{7;SksS)S;>)ik3tmHpy5d09MArkZdl9&o_#wI;aNUc*y~GdE^?>VM1nwn%h^_}* z_abmF@k4Yy;JO!qdx;;S>jBrj2;58j5M2+r?nU5U;)m#Zz;!PI_Yyxu*8{G55xAH5 zA-W!L-HX7z#1GN+fa_ia?j?SRt_NKAB5*J9Lv%ghx)*_ai65ft0oT0<+)MlrT@SeK zMc`iIhv<61buR+<5}Ly8f@@`p**^=JtrkG&~T`WBenl12PiQ-}k=? z$|ID&1r3o=;1N0+BGm(8!NA1E#K6EpB!0kpjD>}bgDB{jxOk6oaR1u>$m)P-{kIk3 zje~)X@mF!b^3SXefdFR|B>IP}4uGov-)D8WlJA_Pvy79>4TZqfkZ-=X9`G3jto|Te zZ|ZisVH-YM-H_cMztx1LuGU@U2o9kg=5J<5)N6;{&Nfrr0Y2xRI~{4(8ji|ZOc6w# z$8FH`H&wj5nU6>xs!b1>wb}K@pJ1Pn=IE-7NsX*S)yT>okDx3X?zvVxsq=}H6`iT! zoL{eTEp|YLvrLNn9btHa-_%&tf!Zw0{OLa&&LlZFsO0_*Ibni7UU+iyv^p20-frmFizL!-&x!*C%&a>si+0tVe zLwEai+u~Dw9j)asW3a1vEI{x>NLxJphYwtdl4`9Sy%c`hGXsl0r{bZ$GV8^{4Ghq@ zu5?si!{r8${(?q_cXhg@=j}TpgBPZe?jo!NQIG3hvV|W~mdFcq?wnWX4J4Z+idQ^e z4D{0YByxa*E~&ZOOBuJtc4{v>7?9g?Oum(QpxFabRJmGKSqT6Et8T{pk+CVi8Z!>d0p} zzI`Sa?-`VO2XND00DnxrLnNSafa!yOf%iqAqZX#P`Iv!3_WM2RqlX;dXkb`%aEZdL z^cg$|7A*Ii5GIsIrnjQ{*>%9e(f>4BUmj)c>M){b1;_$!1K`#{(a$SjInX;mSIV=D zj2*m{YoEGe0SZNq4ehFWnRQAubh{&4d<<0NN8ZDG*hP1Mc9v^$J($4Sg3t%Je2LZL z;e}%Xks0|?tYs`HpB;(p=F^LxEz;Y^VoC&<964mSgkivMl9A(XUq^Qo{;;_+rUjR< zF{?}@aGIKQeQh3WX~&?MlGsfO?kWWPR3-gEc8O~}94+El1B0S2!uyMG<#R8)d?o2~ zrB72OC~SthN;SX1z(j*2!-@=*5hp^xgApT3o`t<(AT02h$9O*B3hN|7&$z7Y;>F=& zlNiL60=471NZE_Cn2voujr6SaXI?$c@aej_OVXsaZSZy7uEAmsMuGvKZ|$tJcbbY+ z`nuthTK6cUrrU%HXx7RmoRPz;(5Nm-Y0TtW=7VsQ*P^My1?5We1vNobe=Q{>WINwU z{M$0ko@$mad94G!4L9~6$f(v&Zj!J}_9mUEdH*+s2aY1e*Mggl6CMj-`7H+AW8{`d zXF(Cn6j3pcQe|(B=OnyF$k4{*bxTKvC8rZm-|RYe%gy!0pTmkGeW1enU{Uu8ZQCM+ zD?W+b@WIGhj3z}8Pv9D|5!>}ZVLG9*7uC{?@wmP6=5IaKg)(}H&#~C31}xoA*M<9> zfQrF|eRv?gPdZ)@8y|u3Gly^>acT2&&qx<9C-mu@SGTIMf0P-WSvtOsCn)!J#8jPE zQ$)YGtT+!3141J|7iSM%hk1@p|1MBj-%N$dL#yTR`bCDBx1)k~jf)H!vUh^+~$uzt#vE|{dy4YAKsVLX?FmXv#9gWxf;q*HX`f{D#I?& ziym-gX?P=9iR`(dsa92OiuBrCkE3a%AT!0g5&&adBI&TkWb@)3;FI(npv}om^$A>N z%%9A2OuccurLx&jl%o7u%v@ZUdn*eq3cci;)Mz0S5tiGTI{;MqF|%A2I;?Zr;kk)hr%R6?#=G^wpTkn{ORfd@-{G{|y6*UL|sV z7d^c20J9bP%?WM!yUr{XC307BcoK{Nl4RjXe z1u7@`yqvQG&_9TJe-gnFHV)|aY)m;=3(vgtGT}yZY!6My9RuWfiHD z?26_ED$N|)-pcW9%eqYZGu8+?*~fU;lI?DvW;PC64b3s^a)s7=-`)Z2CC$aDt01DY z5HB>bP3nnL)h;%=i*Jt`=%Pfncfi9W%SMc4bG{uTWj#55>o)F|CvId0t8NW)-?bes zJ~_~?@TJvWx$ZcPmikZ7kbFD$gt=tHp2{BESA8-|^E>_=$reBnRrwpUd-`2@>05Iu z+_uQDR&w4x$eiJ8hyrBn)Jto9CRiKQc9lVR9qP&SbP9c$W^VXA*zoPH|Fb4g2<`YZ zaaE9XvqI*VH;yRLnvSMR%w)W*A7eXJo6M=TL>sjN^bU9Dfn9F=bANZc_LyfCyYP7Ht zLAGDHd1+cJ2ydZ&&ydH^vc-0?v?9O1ULw1?F$!19oims#`UV;27=M**iN3zuPzro= z0@Q#ppZ};VhkozQ#aVRa9KP6qM2~t;S4v~G%OM6?Qyr~brI5q+sjv1X&sh2+tP?lU!3 z*3`z3@ggPYa7%nNscjRXi;_$y_U~~YehMcV>(XjCk9a+xUj8oTqY=lF$ZCGm{~d+cTv9S*`ylHBt)ea&=YWLwSw0YOi?BE2my@0+<=r4Icp& zCz#84{ZI#58lEPaE(6udv2+mAchD^!GYjD333|zpu%qq68t}*Ny~vRNBiQ$qo|}wj z-tBsJ2Djq%g9Ni^2aavZ?&l)<4Ybf9$2<@RHhlOMLZ9D&trpx?q z-ZNq6*XMqfQW$Q-xNtKlFl2SqSJygkLD6xC{qRsza>S8Dd(gs66V;M&jT&QPG)fG*SBe=@4OJ9oGv#V$?wu}FU!Iitw z%--KivUd3Ob=WQDN^Wt>Ual$mfz|9_68pE;iG8dRZ4>rUEkvXH` zIphi)T?B%cQ{C%Tzu5mA6&Q$kva_OQQmbGJJc{T6v(=Yr0Y$PvI>j^1@K`){G}Q2j(Oc4C15I$SKf41i zidVgIC{_a;Ys0R`vY^A79eWOu%T z)pgEN){o(AxLHWSnZC-b31zG^OGK9_(6E{@JR7KlkfyH=U}4eb8$rWmqRZO za}PC?T_vBAsm2jJS2$Pw2&VaQ#C)C@o^xlOE;k_b>&_BRvw$a1 zzfx$j-oD_(I_dcJn=s0)9fmm1Ra1+n2CL3lbfjzEN&8aEyWm;SxmROFg+;KzpX4eI z$ps?UZ%^4IN_d6ngw6XXsJInT%T7E4u+JJPGpHsD*Yi&)pC*GXH{v&$50vCj{=hGI z1+5CKU2~q=b?Q5H?;Z5CG?BncE2Vz&OIiE6{bVXSW?ehbyYx7Ztd||LRC&5rWJ=ZS z{-{o(0`A8sTP z0{GgGJ`SL%wTV#H?9sPa*GyKiNZJl_B~{{*t#^K1%{XQdrnDp(^)(#lxTS-sPs!a6*wW7L%i0=`nTD_~Z?~KtD!jkgLUW22G_^$NwpHvZ=GNF8UC1Wa_Q|yQ@FmdOzhA0MD8!8m6)0T-8#DT| ze}*wR3z0zfsQlV3nwL;R0yO;Ql1dcGHa}pt%7(ZQ`dM>0b+-$^F3)eru8O9B6avHO zpcc!TNe(Ts0g(wod9WSpIwekevW_Uhe8yR+=-4fVBBkKmz!qm-^efyq$(L3&AKDJkvVJ!Ny-%%8z?;J0T?rdqw7B%PqW1|T<;JgD^ z-T?qxqV_*hY}06uNV=x$`HGZC%Y&Wm+2!61vp@duasgjFkoL5ttY*K;DbMD--Xh<5 z(tF~2Rpq2dA7P&ZZGs@GQ$sxA7Y1E9MbyVRp@(POP6?ze5*kus!|`konr9I-sypO_ zaQ34@m`j%y_pT()5o6g)+zf&04vK_lf~LfvAcI(JuGAUf{xXz;qm96yaN@dAH*67( zjUdx7uSb`upS?8YlWE9s#<=N7;U#U6SOMB&G?Xs@)C!<_;bf?jmj0;vsc@L?c%=)`29Us3ib|_|cRfY0r$KlZp-}(p zwY2u?&UfLagH%y8=qeid{TJ}z2!BC1as5~`AFO_Hg6qlTl3rsZet{#t%8FA^&l^`r z=(;%g=+$axv)E)6lfGYlApC;iLUClx;`Ac3g&|+#(a`A*SjBj2wJF~Iit{FF7Cfun zg}D86Xs3K1t>~}_a((M>9U>-UdXpQI@Kj>j1(*b^rp~^_ILaAQ7Iztb6tJ)uqG%rL zH!2PfH#d4Ecq|;q`wl5;^lu!ylWrT``A?W9RF@!roSloUsLdp4(OF)M?~vL(S9aMv4?c_4%CRoSpanBd!au z{i>$z4iLg?WCF3%*0WM6Et;L5Gmr*dM+Y%UI!6C|qq45;ejIR+xUhDmRS03DAydXS z;+iq5(UB2)B~|_=JoF2O`-{BROLI&yo#Y_RRFpBUt|rx$oWQ2P(&mDz)NPEY+u<|jRF05SG20}oo{O&y;Un^BJYFE9 za8i!P$`O%LmKcvtpPYCN951E?B=td3{K)*Q<$Ni`SENX<9KR1O@tlt@+(dSPzu?6| z3s;GXlaV#lWm()GvnKY5kcKS(aUXr7kD6{Ok0wnglIaO9p3SxPYS&^qT0rFhP7-C} zB9bj`_7%X-1sG2QeTN5QEs23XQ*1PUz{R32zFk6d+46fseb74BZNm zK@>yGcx$3mBN06y{C7rB_BykfqZCwKDkRR=?wM7{Oe&J{~XW*r?}Mg~^03n$qdlwxUAQ=rtXL#xH25 zOV8_$5(2>O9Pa$-#8!epld8z$El!!SA`C(id3fcuk^ zP0VCeL1i=YoGiNhWA~J?&5XG3Tw%vh7SreafvgI!(>^qN8w~n$9=hGf)#A|Ivym5% z8*?R5qHs|#|Aah0;{`ZZ7;!|m+lvHmO;;4X;5JWw}+iV7g_@l`3M0*RxB3i*H-$2=u(9`f%v}CPN<;FADW^G$(poK-kbr`lCcfeKyjR)-A}Zz zgw?`t3n~!TKs~XDpPKG$vBWDuQXxS-k{_1I*Cm-f6MU7Rw>?@xPEQin;+ONiu^TMRq*_SkJHep$YbKwb`bY7U&?gCTXKWx9fH}a?l^L5AqTqI z5xQhlbwPiqVR$f^@H@%cSeaUwlm{XVgTr0t3|%^|!g4Cd7Q2rqMCfts92k<)InAe! zYM(0=2r+#B3am)#e%{Cfp>rj%FFp$cGpkj^bUH1vL}*sT1==EGO1#eJIF17v!(w|{ z?*PQN7#x|qPm-Rx7fc>~f2~RnA!4*ICSDm%dVZR1KWW=}Y1mrAck;}e z@&jl_$~65^O|Za%i-Bt*h1f@`>}T)T7#p-N!e639amW0zw3awA)J=FTz+$*F1^jdB zOzxJUc`QMrlGbcwx%2cR*j8DoeTrvZ=t`c}k^A+r*V)=_z-SZSD@5D{7{U6wlPyZk z8k{74w}y;GhAl*?e5F2v^3QIUS7=3AXd20phF@#{0hiMRRT`?k8HdY5utSPDT*u$W zs?T+&n4B@!KJ`?6kooXu zxSI+3f=a8$>J&5dy0b<*od#?rR!XQyHSaE3T}o9XO`!*2HV-B3UX4nY!&N~J+CgJG z1kQasZ+D1Vd_I24g|%HSz5Jc3Uj}0p?7}mA(81XjljysOgzM{a?gmq2O!3<& zgJAnfLM)q8&MkNf78t|qWy=)8Y@@8Kg~amIHd-l!k^FO6elDl3H%Lhg=a9#~gPbo7 zrmY~RCfZt{iK;7twPiFj^$BUINA@Sa4K=`1Cn7Gb@IkghPL#X>mtN|)Y}ohH!`X7k zVmC-PR2nacokMNr@%|XoPFG*624h@Y+FaetxX_B2j+`%9mQHahM-B)xbDnlS@tn(! z@~H;DLnZRcAu*H$;OR)rVCi#i599E>1CUiYkb}+UZ3sK^1WGU0S053J(ES1a=6g zDNhr$%Y3d7M2ct+s4bC1e2f0{)z@d#pWeThdu!G;;_*~u=^JR%q-pF1g|ARrM;q|kd`gKJ`#aU{2a7OJ71O0rzY^H=tTYcD9 zMRptFxH|BQ)2I*5zP3ja=fw~z}NzXXgU^Q#2EUD6~uTVES~2zrIo%IE^$Dy?Bc9QCt;{&t#B zwH{oRu5Yh^Jlel(4UrnC909||vcB<+bi;=^c<>ad4gUrjGGV=0NXxO}9pIRGeX=a~ z@F?LtjIVs3=?7=K@OfMt$5^D-W{&eBj}j0E<}dLCkvUkuonU|d(!TYV)&M|P#V2s3 z0UzT+b#`}IU^75mbEV0fbS-Fd2cnN1KyQoAx)=U}nQYo~ldM}+?`Sxp8RvFzoZm}t z(JLzsm(hfTuOS>U`nd!pxqiaH7T?U20Y8+dA%%14r#6SJis1#PREpwe>krf__cg|w z41|*~(5cdX{PRiPINbxv+}TpI<*#R;FnNeReMp}gv;Z)Z6<N;4uv?l=TjE1$ol2Mq)mS1oY8rvp{{iVm)afxPfkNd4nVo z#lP)*h~pFkgGG|bo+fHVFrgOIVzat2N1+qM=k?_^NcI#<3pB+iOu6i}gfBfUZYG1_ zSV@xAY9FCxZ^ib~`sD3nx-nLt3cH;6h76AL*|n0K)$G+>6V1atZqkc=UkaS&=NakC z^DEV^C_R_pX&t7#@)uy<`#XEsTn~%MzRA2$y&e2>z`52~5$cL&t@R?G+QYdZPdB8M zu{)t0il-rSI=YoR3D zqux-~FrNDxkz$%bDra_cZ$G=^$fI+%xYJ+9E7ie65n_+i#xl7LUNffnK?IGLc;Qu+a;og?*MqPxjoUInS&~Gkk4m4 ze)LVnsR7m>tc8rWYS}OPH9z$NP#@=>Qk=i2h8}!d{(4H5RUay$8#}`RhfSOGq=u0JtX^6_HgjKSYA4P>y5`G~a~ zR|Aq{x}8hMzQr;s4HRcHF0~$ZAdbsEo^H-w(Qx!q;%HAMhG&>rGtAC$d+>%{)|V&(RqvyaTp@#C{tL&zt1;ZlDyeVPL4u zYERs@yWs8fBc>~TYi%VPCz1ve%Vbl(xkX{qufeHLXp_*y3;LXYE>>86^USr%9B#J* zbpr|12aJ9-9(e@cQp14i`ue0*6bk&)XJ2N%IesQHTn*xgGm&cqFs<`V z?Gaz$DS{3ZL+b%{9v0e{7#6J^c#!2(_za_EMrj;e0zv@{oBf%Pxvf>MyOl=x%b{YH z(Wb0>I=r_v(YfDPM$=&B^)g`wXd6rkgUqem{7iS+K&x*qs~W8w@Nh_NP-Un!YFv3s zG>#It3(SW~;faEFCrl5O6fW6p?f@kr@YQg>ToHCE=AVb~$g5cWcw^VEf>5SHrK^#DZj!ZONUsXh6-^8PSy+r@PaHg|OR#V!XmsqUtCllW&U_m|_(8lXi1C?n zPM@rAiM1S7le|OKRk~7=Vk1Ar9{BYVRDmWW7XEjL%Eg!A$b9_S;)&Ww^U#j2K@7-6 zB9@Qjo%-*@{=q=CL}7qQ6R2C2Rr?PG$@6)X@C(G89kCiPbhWZmnQH@_ zd|j<{s1}fPT-tjg6RLNt~#x)-&5uN2td=41YPzLwd1$eM$f|H^&pmJ(hl zSuC|r)_w(Ul^>I;81ByJSK!!F3Co(7NmDL)qyHvyj77_?7OTMh7yhL~9q}Ow?QJ0Z z5zN-t!6|=f$*9lu5jhTBhN$oTS-+hs1(&|%8pLZ)n|bO9u+*urG)eKT zxfsYQC_Wdnx&tsw-T{_V({VX`rJ*|~n!$aD|FrShLuFhSG26Ys%W?IgAt2m0j$})| zzwy0EvHw&Hp@Sr~=OtVfnehb1Cm*uyDENfGzR?|6+4u3J(y6OVo>F1y_4CrH^4VB7 z8`*v0;`Pis0N?SRnRaE^q0h}GQI`MU5v)R?YJ%(RM#X3UHE~02oC{YnV<_1-q*gK$ P6+jo_T**Z8-OcPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXfT~J9xK~#8NRlRAG zq}h3%cQPxN%B-xdtGDSH%n$&vm>>wjRLBG+Dkz3OIvn&5Q0N~hyhQjjB5eNwhr@qE z{-Ow3j_@a2VM~-qm;?x527~FDUaGtH%6<2B-6v~kXZ72e`7Y-?&;9J@ocGPfzx*Hn zyKS?vEv0gTvd9}-AGA*;|qU<&IO1s@D4c}X>vY5@we7!2i zCj-uJl-Ax}SvNLiwVIV?vspU5{c?1CRQ6hXW!q?SU8^kTORnLZ<+^nC`{nsZAC~^$ zu#E0T>|fCNUfJ(<%WMukt3~PX`QV^mwzN0AyDOXZx-6HgGM`V&a<(Yzm7nKjvs#uV zpVympS#b_z6gq?^>1vj;*+PFQz5RB1_Uwbw>$b~uIxSoFcWAr8c}x0~$JSn_tk+8} z*}$i5?7>5m@7vO7G)kw_$u^!$(uOo09UhkU-lqJEfBkPt<6r$x|NS-%?{`bP+bOfz ztSlF^(gBG2f;t*%ZuiP$IxCa$ymVo7kB%A*2msvqVh%$ze0o^++N}_<+Q2aFwmNMD z(c;)inA}42h9(z)yX5$$NT{GUxMo{slR2O)^L@Qpri~44A0nJC^feL8;o$+mE)n*m zESB@qKuBxOSpn<{U{`*o69I1U4mPq-n*7&AiS_|>#dS?+`QZ7}(AVB$e_J|AS%EjDgQI?Fag5e0V-UXwQG1G%6(iDZ@V zV!kd5j&HWjXvS)V26N2rngA`}>+Ldq(=Fg^qKysuYD4Go$&p8P$JTXIOCfW!=GwGH zo10aD-Lh{3PaFDb@?ERzVNLrz_`qrp(YBM5<1)Z;G9A|MGtZTn=_IlXj)arE_08OOUk;Srvg9Zfj27Nkjfe?0w6gRu;Ja?{nqv2|nATD8a4b%CV`Srf&dt-M~$7<#@3w zcf)D9xf_>@tJ`voFC9)6Ws26$IksM5f@&_nDujnu)Aj%<8i;DfxAIxq)QsMK53}Ac zD}=nz*DjO>Z9t;h9V-C9;meAhwz;Muw=G~u=mdW?MWbf3C4mObVw>_=O;r;N zM$uM2t3B)p&AYNB-J-&Sp8Tj75NKsTlJ7`(I!lB2j8 zHQT>d5`YT6nTD4)tG23{o{W@{FoKJ}tr}Z3nAQV|;N*OmR%;g=TL5SU5tE;<0JRYK zHu~Zm4KT1cwDrH(->G--N;%Bznn0VkHo&Nqb2P6BT^5%e2gw zE=%9%^=h?z>2uX?2oDhXXxj>oV2dY3(>LK^LKdSN`}r?_MO)BIV3RJOh-*+duz>rc zPreJT!m~}^RZ9JNXb6x1C_r;G&(EHyq(dB^l&(zqS?O=+aD{eE(9DVEj~|`!c?E!R z14vY6mk-*DMl5gtD@y66e`3z%g+(3JVfu!7!JN;P_jC+6*gOaK66yt5<3;xqPP|P&Ej% z1+h`6OH)4sq}mJm?H29^+M>B=Fe)fx0;(^B*#@~}gKS_60qjT5B4u;XtsRi#>aQmF z*89-}V9_GGqd_%5DQij#O-EuItJ%GSZ zvR`eUVJRsl(C!Ap+-}G=^uZFR+`n!Et>Z7bDw?deb|?gV$%Mi%kXQui7T~Zlx6^7< zD&5Q1DOp@ktpGjQoV=+%rvlCtleqy{eNDGRKtO}mR63TO z=CKBr4H-iNP};l7%t}W;Z6GjL+`}Z>_}i4ooNwx4RcINjCd9;{TEGNo3rG4oLFIn{ zwlJ_@sHQ2?YMkfu9}1uq2vXf=7*;3QT3!p_eF=P1;?>lf>tPv@_dz$BRX5Y1y}cfqME%S; zw}5Zhu$nXIm}{z)0;*UABhB)zHCyGFpj(%#A?Be=Bp(8Eoc1j-`K}w08MVk)Oq>%1 z$n=d~bB!+r{@_FPg#yg!^?X1I=(-L-@PHs%6txrxz$Htd-2hA$&;fLPq`l}W6o|ZQ zF>~X}a$7$C;>$85W~@=1@BpZN$2w4$q-H7_Zm&%VErV&J*X@^AIPROP`(nkp!iTC| zw-N_h#%DN+)hL4S!K*AG_!bq@q7m{Onh*D-(ia*M|55(JShO&=@3 zSSUVN?W_RT9NcT#Xq@D^DJTb*n#7+q+0Mc&0t~)*`Bhobzg+C$CM_n+yA7m$D!fXA z?P~#m``eeV%RR2Z5;Un8(lS?6tCZaN)lGSSJA@Ja%tLMoc>30u2E+$c8iOfo{FrT+ z2k?NY)2}0F=7UOmrzdD?-V$KU8#t7L!Rl*;Kh_;+hU|@2CrIW+pQ=2YJ}%l4twf8P zJFV8l=~DscMONQMvpTvfcB!#zOwrLX5b*y(^!6{&{d{Q66s* znmj^8=#(!rhb}zANogtxhZ=!}iiE6HM%ZWCLysAhQA664do3t3u&gjA!Bs;VoLdua z{slM$#1>zvi{Z0)n4U?pl1(uY%|;;fx8+1N&BzYD!3=`ZM}nDsKyJf486j6!p@sng z*H+)>MZB~Y+DSv~HFm!E*siAO*gTJ(CVn)$x^m zbp*>zs}T&r(N*|8qE-WQES%6kF^M4V=-_V%aQ8zpCzQ`uhcs2;Kcrz+cT}McxRA`l zg%Q@EHCZpq4XKeD-P@-+L2E%g%-Z-CQN(Lt986 z8k1&0U#dEQuN5NaS_ZQvRpgjMulh~}sC4Bm@FFyoYmT6A??&bIn{$q%c=j5Lr~~Kv z@$B;dUVl*fXzdJU@Em0f5@XE2)9d4tIw%Zv_jCet^VG{-G`36GD2Q!{FxopxR`5i}iew>-9t)%$~bUd^(P71*Xi*W#KZr!@9Rq;b%W11vc(MmwER*eAi(QO{;mOcRF4jhrUw zc5wj^Ztm!GkW(Oi{Fc)mYHkme-e>FJJUdFA94}g~UE!<^f@?AX+g%VG<3D%ks?h>m z&03dL^W$U~fTo~5HB&nJYyphrpxp@0bJhOpZrIQ9w2vSay2_p`0&8fJ9o(Np;PC0lMGomIJQsvmKFS5AvO3aOHJP zz$27JD|9WUY#U6;0T9rQIqJ7p(4i2l0u!9n2t~kq@>tsQ8Yf3vK7W@g)iD)G zjmgq}(+Xrp&{}D7Tp+kD&4Wfp<5B#a?HgU40T58N)<|nk>3brt06I>JfYjiC-0PI1 zm zLIbe$H&K8T9UPjW*-Na>ru9m?0|6FPHCF+dqplkW6+xKU*aclB3#!qcFG<-D02x7p z*rUcfwyQNf6jARkuF4Ecbns`74I3$TF; z{T(dfsIPRc3oCr1PPG|X_V^fk~bO;ur8(jUHHEVX0wAZJ&^}*kp#rIP&5L$hEiwl-;djgQ4aAt+dwr}O%YTpd zj>coI=RAmX?L$+ri39lD-69P%cSIhi#PxL@q-Vft18CALG&QM=OtNL5SZxwmzsHwy zEd+d>F+h~2wMT$Uc45E+~aIXdi?GhD~nQNNs_*~dp{Gj4`v0Mr2y0p?l(lR>A) zHP^~&)uw1vO{wOKS(#3zA_(F)+zB^Lr)4wx6);YR*`9LH2UP=pgcM~EJsH@giC)g z9e0C2)oSb~c_fVbc@Is}1tk5^|91~Ed^v$x)l?s?dFvkyI!-OAeKG5Rj%fpgGbrvC zWuw2KS}MEN!Vxg#X%?c@_+?Fh7FL3uDSP6@)#U{l1cIe4wHda}6&=N=fPjE8;4bJ? zf)2agXj%(^0*1^0oG^y5G#(*GJJn#N#v$ITDXXzb;yvbm&I1cRn$&tQ2~a4JA|mVJLl%W{XL03S97g^qV#k( zcZLMhcM405bFj+c&IEMRLQbp&knIB;7nyxH7;cs4W%2VI)BnO1*VY*owfBBFtR3;a zx#7el=A35bq$6HnLQWH#|FvmE%?%jhlSa9a^AlhoFt`B{EXI7{C`rB_B}5Yfo0yS8 zX;8j;)MIfUoY`iYoUGpsV@N* z@Cot5L8AAX?7a@=w@?RvH#sN@&r`CET}d*xXYjm z`(-fIAGGO%9i|+iO}bIPLvetsK3G8M7fVe}98lfws2}c;wzaP0-SPpwDq}$P9bz9c z7~PB}dv$f4)J!3R-5#mLI8o1{pC- zs5x5EX?l)WXUzcDs%?Btdl^;ZUtCT6ChkYUu~SX20Z=V&lUW=mhgQSj9DvQM_hVUc z3jpH)wn5DGXPUYJ$u6XK2*6G!MayYjE7G6W5xANq=oZx62dx!+>9vHHPWEPOBK4U?pMJURCRFAM4N9y zO!H4i?yVeZdKZU#HSm-YxH z8}zC`g3qx9>79f-j9E?xN1%-F3IJ#e($~yvKtcpm={blId}-_J7yARe9R$XDf&@eB z4qi5c1jp?!izRsJmp#Wu)z}CqXLF1;>%RI~;B^NBN@}wf%~qP#;AlRd^{2WPU6NL^ z$fT(><1!GuQ%TM^$1mHbX08hIFMLV25a}KimWQyCs0r=Y#e4!>V~2h|W7az!GIPxr zhYdKrG@qTFpaHZ38b;Vd(ytzY%mJCqfpLLGU&lmYIGarK9b)4qXeoY2MD0fs>BO^~ zCf{HmG9m$LuxF_4c?a#SnMF8Z1GofDr`l=T83dCe_Zd7K$2cTdN%G3^5RRIM=W;X} zzY%Re>i6T$<`fmKx2-`>TCL_5Wy-Y<&mrG(Xx4n1XEJv#ln|H5*Sd z;G|445eiQ#oB4kA*S2M#y|5kXTJw9a)AOxpEWJ*q)VgQ*n%s>!n@;F2cl8Vo*8DjqmR zu8_3~T!Pa-J$*9T?#P?|R;!eDdvz>eX}-*NmB3XtnQS#H*wH6Vj(1pwtGN~v(mbX% zFt^pMNOGv=5|vNap_nq(k)T3vPAvA(S~bbB*_b3uS3e6lJHo0jwKYB)tb~spb{b&_ z*6utH=#kZ8ugUprYO&{Akc6m^=lkw|+lw&yTzB%ae_O%q)nLHv0-k27-!w1}4h8`O zor`Ikmn#H8ap-i8BB_!I)EskG*I1)g+mG(T_yeKzg1ypR9iTxQGj8B&@?QLR&Xag> z@?v=p|6I$bhkXzWF?*cY@1PM7={Uecz{<3ZHQS|Nj@A zAgnn3bZ(;(&Aa)o?IUfo6bl+U%r(C7O1vtlW+YxptYVJJ z;8(3x`YO-~f_%=Dm5bD14K1~^8nCijrd4x78xpXp@_do^9nv`o_~YtuUBez*#zx?$)nPxH?GUBRJI!u@bsB#*#%<7}RiHg5<--4(ZZ*&vPR}3@ z%^?7Tr`gr2f(@f8fSRiy)anc-sipK3kc`Y3M}YV&ij#l#>yt8E$9K&&VA4py6EHtK zeD<)g@cHjHI1ocGLzv3Cg2_%AO`vg)L~?$Dmpc@wZ)wwuYkLWJ0!>Q};yZpETF_p{ zgL~w|mh3@d$D0F1#uI0O1${oTa&v&mT)ve|!vP=L%xX#+tUgtxsi5h5<_lZv zN1nLL6#OnEz*F1zJZhh4w_SNY^cYaGyxyG;=X<4~b4|1tznQ9x^Br$C-_<1bkM8%5 zBTY?VI0J35p{8ag1?`lt$^DqBZ$%q*m87}ybj&_L%-URC7eu94kWJ{(-S~Y%fYjH+ zK%9wNiUXgZv=bD%qg@kk@{nKaev?SBrT9@Mxu0=!B4sZ|B zW?pM!PLX$YZP7m3KwF*t&{CaI8JMae5bw-!yjK$Y`~CFqGS`%^J3ifKfcJCFmT(dH zYV&88ms%3n&2t?aCxuf8dNlSy5K)wlof$|PwbLw>S8CO6&BWa~Mk7wgi~m-?1OO`- zum%?>|F2qY(_W`k-EX_@qYuTJrT|Nd{b(Ql1>joo?XD92Y+mWU&;@~ef=tfo?O1Sy zzjVDj`7bUY_bwP*I8V_z%4gtn_|ib9KQ^D8jiv##PX^NMK4b)@U;@guaTWM>d@*{0 zTbOYU{K8un(7=fNWFF7Y@Ijw)T1T6#%XQ`{8rbhvnW!gAaQq5;!l=gpCW^{c8D8Y5 zLa1lgmzr!rl48J4`RvtMEu&l5I@DLAtgb=do=$84hLVyQxpO|7CL7wIRnP#-7?InQ z0L@td7m+-yU+^lKmsjc22Rq_tUhgDiTmpTMM`N^h7L9hj#?+e{lOGEaK&wRd4N9)a ze8`8{EqMe-z9+@eg5*)B4`WCDUdBnmCl}bF!6&BeY3$E{2GUgJWz9$%&>=S3xhBa5 z9XA7xM1?7@r-1}Uh}+^DWF`wi=ykjCPDN5VY2n@LEHMR;|KkeiCb(uq4M4L7Hyi<8 zKGO>t-0vOaI&TWG58Z?QSXpENO6VzNpM8#3IOK`5jeD`}@pxKpU3dsM$A>5Q)l%;7 zM^#hdUFC9oLM<>`ut13r6%cKwc|X|#$zt=&|{ z&o#meLXb|UzHHY|*~Pm=`KkiryKA}xT~PU7AvPPXPqTliuo@yzNQ;7DpSs`;F=|lW zqYk_Cji7%RH3A;hl7!ot(>Y1KEO$T$kl{Z}#x+ zp{<&|(`d70=lp<(%tNSpx4R#_>Eb}E}$>rhd1Ze^ldaMaEHYp;Xe zmcT*uHCk*%7VDs$37?(NRg+Z|iwZfy)3T{#S{}lTHu1{J7Ki0>S~bBH%$nK2W86UU zNX{3$dS_(aTsu|4#gPaiZlp?3W6_M%Nw1?VkD)p4Gwj-S9V4YuTH`Ab(L}0O20WHTRy~!?*xQO4Z70Jrz)o zOSf@Go7GpElA695!1S?uu7PNE%g9?B?k9O4PV476*D6e_#aIstDt}wtP(}j(91bBFMI#+$3FqwWqJJgELDxbx)R%}!z5XU9FXGU_{^pz z1X#$GV1tzB)KrPn06hN7eGa?|aE|R<1|Vuc66I~J4&2Y?+j4nxUtYbvEU(TlA?{sy z`Q_{K8oL}JUWC1_nX5;djT&d&NYJQd}1aU!9f@G_VT*4_76~kCc+*jaDC&&<8t=67Zl8? zEi6zO{Bt)vH2&Lv_TOwL^I7@McfK9J_y)!3bv+v%gAR;io) z`B;I37q!!fiHSr*6bQ^F(N@!W*MeQ)bN>D=ceamb^Kyp+X}`h^yuZ55tg=CFbaz{R^7p@6e)RhvrL-O#4$6>yHhlW%sQlZ% z{B>#kSO4_Cq@>=K=g*&`0ryFvT!-t*ktT_@X^c`mggB?V`=mMhrZyzG0T{+&R)CQ~ zb`yHFHX6%Y+=@O3;t`yYUir<)58s}@FRxx*!GJ@51PQdRLBG9&emOZlCC!6voYkAdcl844k6Z5;1v>2Xw44aRN!N@XeW~ z0Z4P&8?^HVkabn&=HunIyn1tyrTfk(FA(G(|NZZj-~aCOTG{sZ!Ec0+S6QC*@BZr7 zxP|}tAN^EteS(K0>{SbN*k}>X`yP{XdDHE1jNe0IICI@c2f(ejxgeV9NR#YYk7w!! zh65}gYbh%dIVJwpwZPZcx6#z9p&d-@QTg~AAC?cFKP`_=kIV7#Awngs!V2n|T>w~7 zeZ|~WHMB|x1GD;Zo3|r_Q`TH$tc0KpaMGBm9^Reu&qO3`WXKRuRg_Hoa4qu3oi(Gu zN`u&qCAQ)w^vn8p$6r3eEj&6s$l!a2{&EK?$NOX|Z{J;)z5nD7{^+Ml^602vKK}S) z0^4zt>n?S-FRKrOntpDI)V%Hg2LB@y8I=M!m57*Zu!LCmp|M;iXxYj48nDO`(2_b` zU5Mz^_Pu>1eOg|8@T8oa97a1G?6$wjWSCrYJRVk)XUm^AD53l z@Yy{UD!3;t69MRR-w5tce)N-{dPm^d*%_KL$Y93F=@Djm!ggFvPfyu@%mqFCF15Qd zhG3_h1zzHln1^1pHDJOdJK}f!gHTB~Z~$$F`MgET2<}=0-APU32qxaS0}#v20#{)M zuneHyF6+9syOB@!m?emLEIB!Cxz&JL!L~@qNS4~2ZKvIy!hT~(gwK9K%{PVPxd3S+ zDnp(X0W&u@!?+QP2`4zcEA)f2BLE(xpNxUij%&nxM9kZFS7q;y0N1E}e1z8MlLRW# z7cOOMuB!$(d5Jp!U?t?tDU|VqKsQ9V_+m}p4C%$QUKkUswoSKDOK){L=kjt5nTEaZGo0WX{(=w*>jSjDQIXs#BCX44k@WKD8!|%Q2IKb zwn9Zwj0bi3sChEH@wfNKfB27onx&O=Fd%ru@p}>Aji;nXn4xKj%bgxZpGHJ=QyBAO zK}T5u(y`J_X%pS`o6!dm<)GI%%A{6EaK}6vUClCle{s=imh7i zPG8UiU2!8Ec96yZ*r7@r`WzgDhF##UTui;h-##j5PrS2~avEMy!MH0khqvcfKuRMr zDR@52!e#tE@8yfH&dcjJ@5}9&2KV;ypB>6QPKM$cVBjAx<87V`Gr|X1emVw_Q7vD2 z<2(ojxoC$w6&`v~kJ~TSYPMqOngRi$9a}Iv`Re_7`RJS9EI;`1k0?9mc|yq?4Y@)9 zUy>Gm@#?&M`to)8`Db62w^#S2=gJ{m$b|AZcfgZARcl+PycYn`P5P$xO3wpLvX%@8 zW$+NEaB&3YjBxJpAh&k0UlswPFe;B)>MQKIDF zrD>Q`-ES#3->{uuT;#qhpUH6VvVJ6)9B>ysO{9ChJymz~ z1z>b5)1l{#u=))Jh*LOftEO#XVQ#Ezk`AduvQnd_CWTO0J+1=i68H?*69U=D=PY2g zU{0C7K=AiTH$VR9X*oUeo{^4nL1?6qXt`LNU*76Qt-z?b0MB7H(;T0t<#L~cqoeZU zAOE;~_`!#f{=*u9qr;BZOR9vLca@>Kl=VBRlV8TqX@ez-nu>(C|4t5c>G+ zfE2ce(qT=>mq`&(N(FOqJ;W`L=h6p0!@D1QWdK-@NIRZTEgj>#mYCV?)j3iH>^3zt zQtJYi6Pi7uvLSFexi23*Juc7Lx*enqky#2v!XX%% z*-B<@xo%A%(dA>GH0OX1PhjRLCFhe9vV_Ba?sMzWr`4i&a5h~r$#p0nv=GSw)?=CK zF|N?AX<*DBS`4mnxqMffv&@PebpV_^SFI(VCLpEsy`(zz&dnbEK6~u=hlNEi?I<@3 z9xdeJ`o1*&`9J%ww=(F!hzlkJ!MkTNcLc}YVL6>o3;8@3YX&}y&6!Vc-q7Eo_4}>z z{K+Byr;X;;0cU%%Eei@)CRQ`7_bj-&&>-r#t*cffDzguQjz$;*!eh+M<5~y>40I{D zwqOd9{*>>Ii17jEWG<^_Y0fl2pCPoi!qJ?D%)Gg~W&vv|!K??1Wf9Ccy$(GVN_~JG zF3QWd7iF<+Wvt;H!QLAWpFKS(-}$y<3;X5i)3dazP)uF>Sk2%5`b}v%laO^{sC5^D zj{4rKi+^hReOr#m0G>QLEN5uR37UM|Z|9t2BzA^ro*nO($H)EB?~*Ohs1~5#iKasE zLlKd_*%VmvKeWd+uNnDw^qzdTh1Q9QOOaeN+`=l_?CWT#9N#Xe+Q%>cU6ggjc)GqUNCQjKClSTq&eBSvGt0Afk3idOfhE|L24Zt zhJijBWa>2lXvbN*EoU-n;1W;i%UFZ#@I{D|8$y%}scEi?>3f&veqD1QI9<#?_XZnm zX+y9Y(9&dc>C+O6nY#oZaJys}J~(rq(wwGoC$s?#4#=q8PCPpCn&&|RuImn5vY)4f zU8h&gi}0YOV#AcBfh(Hu-! zj>rUPqk`t5HKm{2a0O#7SD!-^jiSu~m0T?5HKY#GrdZWmjPFT>i7n{BP1c*tQO^fzMh<&lqZi*P$t(VNRQPU zjH1A8tlxN%Q9p2tdukC)>(sJfSa_*De9ykRaLpw?4o0|0&r!2J*@3|?gESCo!-AA& zb}4dRDScH{2u-l~8g#Fz!NqGdUm?hCu4t;kz`mVHh}(du+&c^a?lX|;e~rksn$nl* zgDapufm9OA0R%SB-(in`_Ehfz)otlEOuMRyTNGG+dzIj>&|dwuhUe;aAKI+1Jv})o zM-FmFbIl&i^kKROFipnhU_(_FL;N80fG9YOh*K> zx9`iRzx-8s_vUT-b(w&Vf3dyv%`3}K}ZH z0FWpgP$Y^*avngb!CYsfRSy*kox`lffxv5uN+#{Yq09dBEHd{+;>2P;I6Xinhg4=c;8n z87T=oSH|D4Wz4`zNETr*jfQwMnRC5dPy;NRrt4<)5KD1C-#xCG)jKNd`Brohcs6KY z3+eVHO?Jk2`!-!_#Sc8tBM6+Ma3T@@**-5b1Qi5*&#>nGbWv_@M|tq_(`PRz<(p+T zC2K;<>ok%>rBsw!6!APL`jCDoxHXIsOtf?zYJQCQMaO>7667^oQvPJ(o>_NP$UIIA+F zAn++(qEb}P$C)V%_#kiYvE4Q2x|8-4K3K6E4iK)JxA&)dM391ih5z+|@lGiXNI}gU ze8u7|g81y(Is0`GETy(tKqX3`TX~BBEHyLah@Z0*br{m~^XS2V zWe#2V={#pqtF(Wvi2?v@m0kV|O>qT+i(nO^&sw{_y(eAj&`!I|>EG!aAE04DbaH$Y zC9_{=CX5R;s0%uu6OjcsZ?d3l`9_Cc=qn>cymGGt`;<=AuT)FeHFRZ=33{qX6=)T9gVJ4)ObC zdj(@2TvNmvD|879s#tm1v9H3}QH+M}V4KEu)(!_|u3uAereHUU<*=J(Y!#&=mq;Ca zTuWMDqw_owj*gurO>mNQ5y=@`13?Nz&B~JgNTEKAVmuSzX;v4S4&}aFWFX_-HDfE) zN5<&sj}013+2TJH0_WP38t*GX2AUqMv#ze;I1PJDa9w2QjKc^mRcW*wS6V8rQ>wC> z=c`b*vc^5MbFJUZ5d7JOD3nUdtErj$lJAgBqx6%DoCxvfvL2pYP;bIs)p{JzyvPD0 z*Qo*gCB?!lGboa}9kav-8qpu_ZD{%MGqf%xZrb7+WRu(bX;)g7Xn{+toJMlV{EBi^ zi>d?p5C(Dz%O#s3TczN*fiVv;-q!Nfb+*?0Q5t{_m0Xwn*XtA(pPeMNO@D4-G@4Po z^sIZa7%T$bwOB7WJ3GmDhcu%gT8(aqk9i)CLfR1QcyBr}sJUnATkS$}ks6Kq>rd-N z^|hXtTZiDqDlKXuVY&l_!*~4=s3lUm>cj;kpv`%lyjkgn9CLLS zAXg#-cJ8r|XLjmp>Jlct2kOE>38&S`Sj#p2uJdzP4+W`r(eMpohk!{Iq$b`z|cs%cv8*qI6ks}C}}0EoP6 zE-(Y#ey%gIe1>~@(OTle2{z`<)wR?C70=0IRcUAGS_H05ZLbT!`YXsZ#y?lUnAy-I z2P&2E!LdqC@f`m1U6*2->1Shtw<5Sk*fQ8DBKb9zIHqCat`n;T1m`1oIH-c#3CNd< zVjy&1(+3k@+}>h(5{vW%1)?RujdG$KOI<#t96z&>`=B)^9uR zZE(<~)F<}~e1~gAyV703t=Id1ex~4_hFzCs-dW}DI}+gI#wy4R96O0daE>#;1{#-@ zy5?Xe{D4*KljH=h?Hy3DIjn&OaT@P*9;_mm!0%?PQ3Mqz%cLOFO}=F!shLW`dwz_G6Tuwt>bZV)eS)is=JY*Q zaiD)j0~2@{Q<3FY3h}Wu*9JPSaebJy`V!#d4E>GwBU8nM0VUu)BV%Z;G z#b>ov9?Uk>f|m1o_&D8#DcG}qg&d}faPX9mPSFwt_Vbzp&f zS8%r740D*e>$L9gr<|k-NQ7t#sr5e65?C&FlleqMsbrUp8__3d?lf9lmnZqlDKegG z;!_P)wf1(sRfaGTu6eFOCY6-$0?K(F%VyPuU<`gfORU@P1(Y!U5(&l%X7%0ZtLs_EUkc~yTqT+GY6>l>`3SNyr$z4m_M zi3>|kP7g^h{XLDUY$|~pM8RKGNPmm7{%oYxDsKX$ge819#V2CgTXRwXiqC_QI#;Ip zo5<5Z(v)fz`XBRGe>^E;uWBdFIVBS}B7byhkkS4e5(>{8Qz*{6Nt|><2=;5syjN`8 zul5q$x;|~mWls)jR|{$bvRbOf_){>?l#G0-&0+(6bOs>~NShbFdjBqC z11l>Z-s?&gnTJrMw(t%a{Ep9{@o|DiR<*7^YSn4^I^5;}dL5S)BtWDy^sa&mi@=r1 zM|=R++xNqqXtj;&yt+Pg zLT-fJrS{wM{&HOI0C(enJ^kd~2uRTt_$*0t+&tx_d{CqYTC>7q&}%?dm}LxaV| zBD0=U^X5dtVYw&oy~B?g8Py;!Hc|_41+ATN&FhPsGQFRX%fZMJjoso$fw0qX@SQ+q zbF`2(uUu!KQp*LeL;jfYPR{ij_GH?-z2ORg^rsJ6Tlxo3peyaId8U2}O+lc!=N4^Q zP$}ttTvzZgx0mTAc#?7d>NJc8_(owV(*YJiB)D<({OXF06bwNd8}hS`rj!I`e>vcT z7f&eF$sooLXqid2Z#LWl092}??v7!8`Q|dNAwG^{{>V>O z{UHO(zYgb3NVe}T@5<<6Tqf`D%JeY zHqpopMaA*qQ8_xGO4<1?iS`a2sG=n7lqH#fw_|Fk-oLP>P*}0eM-&xoB!^`)!=K#| zY_6weatZjClQO-ca6sc{x6`s5;$lXmZTI6cy(57Kw7ZMza{2C}+}++`u7*|sn61lS z|3AMdzxc)9X3g*qfB1bA0$M<~-zPpmpEJREmb&1GZ8TR#E6Q~ws|IVwO~9ip(E7XK zy!`v0eM)ZIp;Dzcl_)MylosX1v(xh7)3aFO?e%rs#L@8}Hx0VFQOk6xQ|(jXlq|k} z`|bwHX(WEvqi7^omnGkS@#*JTh$egzr(gB)QMq%4&i(0ZN1|NQTc2}408kqMeLlps zyc?G5FRse{m$zm3dRXQcfP4e+_scRzLsuitb9w!cAUdAcXWY6z?Q~F{e&fRdmmO&0 z^1UDYUirZfe@Nl!M<0Du{)_+oKP$&a{p7q^I0#spzV0mn3_6o00zQf< zyaCpC$gP{Zy%J6StG|IywAWzt@H3C5a;97TgARR>a?$p1c$+lNBEfNwD4cllGOvy3 zNCnkS>jZnuBLVcQp6ts3j1~dni}Uy8<@-wuS-79cajJ3@VGzJAViTaIoWpB|OH z0jUd2jptL6d^B}F$%;lJwx*(=^6`H9t;N=R9AN6CR}Rkx<;k}`EZ_a{ACzzXN8c#} z(jIUAbHwb^Uw>ML6gYqIdp{_D`X_%>4iDQ9Z%w}DxaMwlA)`CZkqu~M4ztnjTN4s-{Na;i}z7j@HokuTlv94vv={!>EVED$s+LcW>4NO=7JJO_9C$G=f^e2 z4TdF)SR|uqE|`U6G?=jjcAF@yk;>10{o8VRi^{VvKFR|U%(()ZK*!f58N^<%Ri1wP zqw@UwpOkO?{>eWmAO7L@O7El7(l`UC=Y#U-dmoj@-~YG_K6zA5e)og&$v^nL z^85$iEWPKa<>bYa(m6ROTUSbA6-cuD<`=&#E!@zzf9Dfi!S~A1$v&>sf) zcq%Yc!ur!+2qFI4qCz)!H17a`x*Y}lc(jQAAYAC ze(Px&eB)^uHx}jb$1lps(?_L^-}3j)44D3&+W7je+!9dVeeoKXF)E*Yj6gWAchmv^ z@AMHSHr{1Q?X5X742CD&fdR`I28~t4kZw~YH0%?%FS2r%iU+^WlPXIBEYllVHjVBiYC5UvLM^pnf$QEmuq0!{=2p!pR;9WpQXwY!b- z^zmb~9L9QYg?+8}sVip~>%u(Ky%7=5~CvI}ZrXm;;~L1lxV1>)UdBep9vt zxAq8PZkA<#(=PoM0n5cI1N_?o8cp&`(zX*S9n+xzxWxrPPw3a5swuvQX~1>d0T=BW z1msNrjqN)G&NtD%>MUY`^$RS-H8~*@zA(}*5}axLLw^+ld7ybFvw*~G__0- z4yo7n)2q__?7DQn7?y+2hh_7#w`KlsUzXKhd|CQm4omaZeObS{EprNmt1sUtw;ht( zF7S1oNBj8ZL0Pod&;d=7YJUK)P-~W8#5U~`Th1Vj%YaH~h6YFD2*3)$XOkJY-eV>H zIH!}3(yyPlEi;9Tqj28oA=wMTxsOu0vPakE&4b=1CLOuI*B95jyh#O&Z)a2BrmTjy zd{j3Eyv_Nhj8rs){@4b-D$D_vEcBofG!t>QA~O*9Uhj@K zR-SxtR1Us%TsFtevfP_NxfZ0RA{5NRq~1VcPFoR1OQAu($G0#Rfphp_Ig|>cYc0_0 z4rf6t~;{+(q%g`vaIJVqNLE=3Q4YaoYfS!v%9H&lBi_6IZkYHh(_U}}Y)>Q>r z3yVsVR5ml~z@M|RcB<5M%udbdv-DlS9CNT*`pZv0E0+sz6LnZd*TDaXs)>JWV9L|8 zW?A5_=4}8Zb+S;9VN+xcd;^^;{OIPeSvt=T%kdAMmXqImUK%fYTm#rxfH`E;5px$O z3x$l1@x882gb9F!)2c1fAyBb8dZ9lRYN`98otH0Pqk%rP$*~fcS4i;K$07woL-xK0 z`8j)ZRKD@zgW|KvZ|_M9Z*J)i1UNT^jcYBEr360ufp^-ICcjsgrCJCs2lZ6?pyY;p z)?aWQTI+KRn|UOPLCb-U(m5#q{+FKv6z4&piz7^nwrK6L%U%kG^#}W9^I}j|FAmD$ zd9SQr^vm``!2f8#c2HX1epL3T45DbX>^x^ z)A`w4H5Wt$H94zVjG|oK+yl}*+Uc(os(I=@@BAqkt1WN0a_@kkNh{TC z?*~b_U9$lx5lb$uSox1ltSYr02Nwa&oDaK{vL~ckfv`2c(0daSRoP{(Yn1%e%Xj6? zjhf)V1s!TGR4w^QZ_T*_Jckhfh&1X6+f&@e({5S2J#Lj1fUeqCN#A~ ze>Jg^DX0&ndHr4)bA!)tV!D+X;G84y`rdU`&-hePmtc9WF(n&ksrlYsN}y0wv8_q2>WITyB#I-5ntx9J#QG7 zP3Zf9CSGu8O{rt^k zY4z}<5)PnQugYg969v#8{d920yK+*hhA=iXplTw_n!7@a%C31PP^I4rteY-EP&G;Y zsp=s0`c!9rO|P=9%tazAtP5dSLAr=fmtoUshkd%fYSun^$>)Y`%F)qriiG16-f;u| zER9bdbzJ-_5I`xprvpF+G|2n)I4vd)q#0;fNY^ z)YL_nJUBcqKmYA30$ZK2$S6N;sI|6XTagp=WMzu!oxrl+0hjnEzQR`G*GGx$k>PLVP4}JRBYkyEqTItJN4|Rh+A8`_DeM1&3A160>+#Vs5P;0j z)^JR1PreG%^d6>zDgXJM|JbxFcIw|C#xzkatv`84ks$D%M;j*kUbEu3yZoUJT~|Iw zU|I&gI$H0u5sVQfzP8uzM^I)LxHbbbBY7U zJ}*lE)s`t~NteRGJ7yGX@?Z$bWdbZHd~>A8rJjHB^WPwAL9L}AEQm}9$@I1(SqLuq zXpq&U5P;yj14tD?oOf#to~CW7*HLE zaRG^1oS9k3&?=-p57dBu+pxWiQ7A;3whB8vJ+5-M@56> zM1E9^D2N^LstRGd9#@mGN~0?uA=VX%fJTSYF@6?;^?m*zh1H4XA|aZhD^W5a4e3+f z|J8r^WqE&xKvwMI`iF7UrK=`+uiyDhKaO}wxKwC*C{hOL{Vub*0+&GO4A=BZnJXMh3h6#x#0ccfa~^8CfqjCeWV z<9%k{Po~LtNzW3Ly9!5PvDZ{}<8VR!i!~#a+#j;*la2y>(#iT7p&+Fil%g-tH4=uV<_BzyIa0ITt`-K45ZPUC_sMT!YTiDJ=yoH4^O* zOpe=ssw&XTb$mxj+3x@hmO`7e-MX_Z%C>(%m7yLbsc|9!&-OUi=^UxdEu^X=w5w5d zlX=grzYuHX_3X)G+VXcQ@AE7lGmJV3>GgGXLWM|1W$)?&|HVZ@t9T?E+WB00))h8= z8dG|OSR3=%UTsn>39UDJz$l%$PBxGIr&Bf12kI!!MhlI>NB_$fX0=R zwvnYqwy^^)qf`*2&?1EF=Xe%1SM4=#RU>?lCXpJ|A9o8nDF$3C=774-FcUEU1jShp zF;L4=u)wI*mkryG=c!pn{q=H|+}4?FfA7r42&NzUJ?tGMt4YB@P&1!SFZKn9yk(!8 zuR1EH>%%o6VBv=brPD$)W?X<~q1vrI*Iy~gaT4=;TtlCH)?fX|d=Y^3_}=dJYM#mW zJ%Za;H*Z+{FQ?Uca} zKi%cCCEV1;I)zVARIVm3Y9+5^HE1u~2RC}id$J=mJ-E8y%o;@?^u2ooh9SXh=&Eld z;NrKlvnMbsKsG=xG@NBnN>3jN6P<3qj0tr9D8AptBMI~GiUA3-29K6;) z@-cvG_H$RF!=#Iw4YFD7&UyN>EUSXn%wd^J#{|_DX{c>QYoRass~#vos7;tdK&Bsf zGw=4o-}%PJ;*qjUb_7_`_< z6WZ&2U(*>mA?^U=Z-=9peSTeplXUPt8*w@vrGb8bkPaxdA}J&klV0QhuDDKSd)lrtTAxa<2oL8g~LXF7;2KgHAy-UGhO|dAuy3RY=1^!15(6E84L{ z7dSOk>(WgGU}$!qrLL}}{@!M*JbHAB&|0_$gB#Wc2F@GqQwj@g(liAb&~{tmh7k(z z`JUgQ00aGa4uF@Z>n@runQS&YsdTJ1r&Gm+M7&{Pegyd2+%ra(h4> zPoNu3G(cKB;Q2$78Pfw`YA!AEGN*@ncIJ_`T9LvqD?ykK3q_hv+CdiDyDaDLE>l4C z@t-I{UA@Q2E40#*(z{s%vv+t9&|F+$?rJ|B0PXbm%N?!WZpb2Nr%g%evvJiF&yzrF z_v53Z_|EuS(?2K%FWwE6BBtrF6QZ-=LX7zC{9f)W_JU}xK}^sfmg@x3oHrmsRLyD4 zZE-zl7zd$wVY7M4b^oB7>n)-joh?d9YIN7CfJsq0=GXnz#g3JbKzdqCOb-UO+T8ZsqQp*<5d=N{NfjRkXt7DU{>n)}fAmeak+dv6M?mvGn-{wAEgj`!&a>@AD+37h z2)=zeXktPJfq-qQr;;FOX!ij~AAIm4bAt-t?iOps^<@#zb6LD2aZYJsU%K7c2jJZyqWxABy6m9lSN1s;~J4B2$tNF{1GH8Jaml|6 zSv=+wS6!*D0tk*7fuJg;|L-e<=kRR5-a@MYqGbG;+3508;5dBf?evz7{?L!ZfhT8Y z(N?9J$63)}m-I(sBTY=l;Bta_`y5xu zaM0Y^-AsT@=gfIEqd)^Sc{I_IT#@8?y=?J)oLBQ^-AzR{{w|tZKdZsj8pui-2Fq!l z3--~AXAx|pu`KT{-j*c*`(2l%;IqmR;47{4w(CocP1=?33YB)rsLYw;A}}=vmgFhH z+(MyY?s$A$j!#YqTyxMz!<3v4XFEB};^(6yl4>Gn#%bBiAe3lUh`W3g{=8KRUn&YQY zE+hpiIR1xU&vR~V-e{FqZ*RDunoJ!mNBf{OqxG~VDEjcqh=f-6V?P4!XZgvkR4}3j zsTnRl@sY1wh&zBzv_!XHAj78tvYI0Z$@*zevHIlDZaFzU24rcXNV*@Too=_Ee7=s| zs;yic;IvsSXJ>VQ=&=ekhv-CnIJo<6e+t_ExyDiWnRoHvcQOh_j;CMp;3;{aKE)Qi z+tD0QGcgl5IXy{$dytgvX^!43XHLArb-YD;-`|a~1SBA%k|lT{1VSB>gt$~S(LKH+ zfJ`-UKxT~Aw(sY-3Cm>O{qBIwLoq>oNh~m6)!V7+c&qCjxRUdI2+&zMJoHyn(OS;E zxxPXt9HvC8#e+6&Ej|+(oGg&{QGFq+l+197aIWRQYP^YYs`j z{4HE>au#G8K6`XlUcUSSjUL5c%7ux#IjhNjOofJhG$4TtasIvn2S~OXmrTMhyF~3xWgbB1eY25B*zZ$03da`D$I}jLYXJ-|pH0Xc% z^SL*VA5;7gwB*f~v%AXDZ%*gfrloAM721;)-BJZEMDga?^|J|pLYB@tT4oDco65pB z!Ho%E7tVwxVKzX}u$9W`>7(-G>63DQcUO)M50lT@_QP+EscDIlG^9ZoWUSl_5WNFj z2C11h&-s&r-h7=zKIhlB1Oe!E#=>^_2SaX}IeL>5yc- zQN$v`n@k*2bpS`P3i8*QL&IucuNBI%!m#5M5ORN{i+#dg{sxO{St^y~=J_7+Uc z1Fbf}9vmLwyPhO%GoYE0+3&*uxMKyDk%F4r`{fRXEhb#u=rodwn}=mcwa0M@b;**N zslNgoiHDwtnc>`E4YPT0yw`B58tM7E1eul@wMoVn0^4wz=%(}z_pv-5>7s-1S5)rl z*YVZ{nZqE1_W6YYlma%Y`=LDrCQw|sqf6if+j3qPg_5v!aedMom&$c&edyyF+*^%` zcI@!kAywDqo zYRwGh#@T0pOOT5%P})eEeq_s>_QB7_G@;LZ68|tuQMHWgcP~Gw_4S1{;X^g*0|0EA zv(twUw)c@*4x>5`Xm#q!@rNm$-4&Hd@@R@os91kmlTeGGp~e&AC(0HA70hW>!NC#iq=Rt*QrUikl-kSoKSzI zYFT;?aV8E_S2G$;q6oSc2XCstj2pFtuK?Su{9rM<9|gw0l(gxiYIjVKcS)`K;a{~% zl|)zTB_V8yR6qX4i}Ka0FC(dp7^NT1fM9)UZQO(zpwLP)if9qc*oINw($WDDwZ_4B zpL>{8h2sV&A3#4_*6ABfQZv%LB^**{9?Ne_dPflSxL-~7rE3V+@(Vg? z2Uqs=+2aJ8jM?H>og?!cY4l+f^MyYsVr+2cn!KF?#a}Ay?(6H-U{AL%t*3%*^`}y~ m&%cnFcWJGBWL#vsE&ngH=_!n+oLR{L0000 Date: Tue, 24 Feb 2026 08:38:54 -0800 Subject: [PATCH 13/19] Remove person photo before deleting record Added calls to RemovePersonPhoto for Staff, Board, and Steering Committee members to ensure their photos are deleted from storage before their database records are removed. --- PC2/Controllers/PeopleController.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/PC2/Controllers/PeopleController.cs b/PC2/Controllers/PeopleController.cs index 98849279..59ca7676 100644 --- a/PC2/Controllers/PeopleController.cs +++ b/PC2/Controllers/PeopleController.cs @@ -188,18 +188,21 @@ public async Task ConfirmDelete(int id, PersonType type) case PersonType.Staff: var staff = await StaffDB.GetStaffMember(_context, id); if (staff == null) return NotFound(); + await RemovePersonPhoto(staff); await StaffDB.Delete(_context, staff); break; case PersonType.Board: var board = await BoardDB.GetBoardMember(_context, id); if (board == null) return NotFound(); + await RemovePersonPhoto(board); await BoardDB.Delete(_context, board); break; case PersonType.SteeringCommittee: var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id); if (sc == null) return NotFound(); + await RemovePersonPhoto(sc); await SteeringCommitteeDB.Delete(_context, sc); break; } From 42478337ed845b9dac95cdad94af60dc46fcbb99 Mon Sep 17 00:00:00 2001 From: Joseph Ortiz Date: Tue, 24 Feb 2026 09:04:07 -0800 Subject: [PATCH 14/19] Update PC2/Views/People/Index.cshtml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- PC2/Views/People/Index.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PC2/Views/People/Index.cshtml b/PC2/Views/People/Index.cshtml index 05f3a742..14fbbbdb 100644 --- a/PC2/Views/People/Index.cshtml +++ b/PC2/Views/People/Index.cshtml @@ -44,7 +44,7 @@

    Name Title
    + @if (!string.IsNullOrEmpty(item.CurrentImageUrl)) + { + @item.Name + } + else + { + + + + } + @item.Name @item.Title @if (!string.IsNullOrEmpty(item.CurrentImageUrl)) { - @item.Name } else From 56c44ec1726354b16ccb1944d7587d8580a10063 Mon Sep 17 00:00:00 2001 From: Joseph Ortiz Date: Tue, 24 Feb 2026 09:17:36 -0800 Subject: [PATCH 15/19] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- PC2/Views/Home/About.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PC2/Views/Home/About.cshtml b/PC2/Views/Home/About.cshtml index 96e3af30..fe1dab82 100644 --- a/PC2/Views/Home/About.cshtml +++ b/PC2/Views/Home/About.cshtml @@ -59,7 +59,7 @@
    - @staff.Name + @(string.IsNullOrEmpty(staff.Title) ? staff.Name : staff.Name +
    @staff.Name
    @if (!string.IsNullOrEmpty(staff.Title)) { From 63f2fcd6369b8c1bbb3b40557b136d8d57346a28 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Tue, 24 Feb 2026 09:17:40 -0800 Subject: [PATCH 16/19] Refactor PeopleController and related view models - Refactored PeopleController for clarity and maintainability: moved [Authorize] to class level, used constructor DI, split CRUD actions into separate GET/POST methods, and improved type handling with switch expressions. - Improved photo upload and deletion logic with better error handling and logging. - Promoted FormFileFromStream and PersonViewModel to top-level classes with cleaner formatting and documentation. - Cleaned up About.cshtml and removed an unused using from AboutController. --- PC2/Controllers/AboutController.cs | 1 - PC2/Controllers/PeopleController.cs | 413 +++++++++++------------ PC2/Models/FormFileFromStream.cs | 69 ++-- PC2/Models/ViewModels/PersonViewModel.cs | 125 ++++--- PC2/Views/Home/About.cshtml | 6 - 5 files changed, 302 insertions(+), 312 deletions(-) diff --git a/PC2/Controllers/AboutController.cs b/PC2/Controllers/AboutController.cs index 6d1558b1..fc974567 100644 --- a/PC2/Controllers/AboutController.cs +++ b/PC2/Controllers/AboutController.cs @@ -3,7 +3,6 @@ using Microsoft.EntityFrameworkCore; using PC2.Data; using PC2.Models; -using PC2.Services; namespace PC2.Controllers; diff --git a/PC2/Controllers/PeopleController.cs b/PC2/Controllers/PeopleController.cs index 59ca7676..08904e42 100644 --- a/PC2/Controllers/PeopleController.cs +++ b/PC2/Controllers/PeopleController.cs @@ -5,257 +5,256 @@ using PC2.Models.ViewModels; using PC2.Services; -namespace PC2.Controllers +namespace PC2.Controllers; + +[Authorize(Roles = IdentityHelper.Admin)] +public class PeopleController : Controller { - [Authorize(Roles = IdentityHelper.Admin)] - public class PeopleController : Controller + private readonly ApplicationDbContext _context; + private readonly AzureBlobUploader _azureBlobUploader; + private readonly ImageService _imageService; + private readonly ILogger _logger; + + public PeopleController(ApplicationDbContext context, AzureBlobUploader azureBlobUploader, ImageService imageService, ILogger logger) { - private readonly ApplicationDbContext _context; - private readonly AzureBlobUploader _azureBlobUploader; - private readonly ImageService _imageService; - private readonly ILogger _logger; + _context = context; + _azureBlobUploader = azureBlobUploader; + _imageService = imageService; + _logger = logger; + } - public PeopleController(ApplicationDbContext context, AzureBlobUploader azureBlobUploader, ImageService imageService, ILogger logger) + public async Task Index(PersonType type) + { + ViewData["PersonType"] = type; + IEnumerable people = type switch { - _context = context; - _azureBlobUploader = azureBlobUploader; - _imageService = imageService; - _logger = logger; - } + PersonType.Staff => (await StaffDB.GetAllStaffForEditing(_context)).Select(PersonViewModel.FromStaff), + PersonType.Board => (await BoardDB.GetAllBoardMembersForEditing(_context)).Select(PersonViewModel.FromBoard), + PersonType.SteeringCommittee => (await SteeringCommitteeDB.GetAllSteeringCommittee(_context)).Select(PersonViewModel.FromSteeringCommittee), + _ => [] + }; + return View(people); + } - public async Task Index(PersonType type) - { - ViewData["PersonType"] = type; - IEnumerable people = type switch - { - PersonType.Staff => (await StaffDB.GetAllStaffForEditing(_context)).Select(PersonViewModel.FromStaff), - PersonType.Board => (await BoardDB.GetAllBoardMembersForEditing(_context)).Select(PersonViewModel.FromBoard), - PersonType.SteeringCommittee => (await SteeringCommitteeDB.GetAllSteeringCommittee(_context)).Select(PersonViewModel.FromSteeringCommittee), - _ => [] - }; - return View(people); - } + [HttpGet] + public IActionResult Create(PersonType type) + { + return View(new PersonViewModel { Type = type }); + } - [HttpGet] - public IActionResult Create(PersonType type) - { - return View(new PersonViewModel { Type = type }); - } + [HttpPost] + public async Task Create(PersonViewModel model) + { + ValidateTypeSpecificFields(model); - [HttpPost] - public async Task Create(PersonViewModel model) + if (ModelState.IsValid) { - ValidateTypeSpecificFields(model); - - if (ModelState.IsValid) + switch (model.Type) { - switch (model.Type) - { - case PersonType.Staff: - var staff = new Staff - { - Name = model.Name, - Title = model.Title, - Phone = model.Phone, - Extension = model.Extension, - Email = model.Email!, - PriorityOrder = model.PriorityOrder - }; - await HandlePhotoUpload(model.PhotoFile, staff); - await StaffDB.AddStaff(_context, staff); - break; + case PersonType.Staff: + var staff = new Staff + { + Name = model.Name, + Title = model.Title, + Phone = model.Phone, + Extension = model.Extension, + Email = model.Email!, + PriorityOrder = model.PriorityOrder + }; + await HandlePhotoUpload(model.PhotoFile, staff); + await StaffDB.AddStaff(_context, staff); + break; - case PersonType.Board: - var board = new Board - { - Name = model.Name, - Title = model.Title, - MembershipStart = model.MembershipStart!, - PriorityOrder = model.PriorityOrder - }; - await HandlePhotoUpload(model.PhotoFile, board); - await BoardDB.CreateBoardMember(_context, board); - break; + case PersonType.Board: + var board = new Board + { + Name = model.Name, + Title = model.Title, + MembershipStart = model.MembershipStart!, + PriorityOrder = model.PriorityOrder + }; + await HandlePhotoUpload(model.PhotoFile, board); + await BoardDB.CreateBoardMember(_context, board); + break; - case PersonType.SteeringCommittee: - var sc = new SteeringCommittee - { - Name = model.Name, - Title = model.Title, - PriorityOrder = model.PriorityOrder - }; - await HandlePhotoUpload(model.PhotoFile, sc); - await SteeringCommitteeDB.Create(_context, sc); - break; - } - return RedirectToAction(nameof(Index), new { type = model.Type }); + case PersonType.SteeringCommittee: + var sc = new SteeringCommittee + { + Name = model.Name, + Title = model.Title, + PriorityOrder = model.PriorityOrder + }; + await HandlePhotoUpload(model.PhotoFile, sc); + await SteeringCommitteeDB.Create(_context, sc); + break; } - return View(model); - } - - [HttpGet] - public async Task Edit(int id, PersonType type) - { - PersonViewModel? model = type switch - { - PersonType.Staff => await StaffDB.GetStaffMember(_context, id) is Staff s - ? PersonViewModel.FromStaff(s) : null, - PersonType.Board => await BoardDB.GetBoardMember(_context, id) is Board b - ? PersonViewModel.FromBoard(b) : null, - PersonType.SteeringCommittee => await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id) is SteeringCommittee sc - ? PersonViewModel.FromSteeringCommittee(sc) : null, - _ => null - }; - - if (model == null) return NotFound(); - return View(model); + return RedirectToAction(nameof(Index), new { type = model.Type }); } + return View(model); + } - [HttpPost] - public async Task Edit(PersonViewModel model) + [HttpGet] + public async Task Edit(int id, PersonType type) + { + PersonViewModel? model = type switch { - ValidateTypeSpecificFields(model); - - if (ModelState.IsValid) - { - switch (model.Type) - { - case PersonType.Staff: - var staff = await StaffDB.GetStaffMember(_context, model.ID); - if (staff == null) return NotFound(); - staff.Name = model.Name; - staff.Title = model.Title; - staff.Phone = model.Phone; - staff.Extension = model.Extension; - staff.Email = model.Email!; - staff.PriorityOrder = model.PriorityOrder; - if (model.RemovePhoto) await RemovePersonPhoto(staff); - else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, staff, model.ID); - await StaffDB.SaveChanges(_context, staff); - break; + PersonType.Staff => await StaffDB.GetStaffMember(_context, id) is Staff s + ? PersonViewModel.FromStaff(s) : null, + PersonType.Board => await BoardDB.GetBoardMember(_context, id) is Board b + ? PersonViewModel.FromBoard(b) : null, + PersonType.SteeringCommittee => await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id) is SteeringCommittee sc + ? PersonViewModel.FromSteeringCommittee(sc) : null, + _ => null + }; - case PersonType.Board: - var board = await BoardDB.GetBoardMember(_context, model.ID); - if (board == null) return NotFound(); - board.Name = model.Name; - board.Title = model.Title; - board.MembershipStart = model.MembershipStart!; - board.PriorityOrder = model.PriorityOrder; - if (model.RemovePhoto) await RemovePersonPhoto(board); - else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, board, model.ID); - await BoardDB.EditBoardMember(_context, board); - break; - - case PersonType.SteeringCommittee: - var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, model.ID); - if (sc == null) return NotFound(); - sc.Name = model.Name; - sc.Title = model.Title; - sc.PriorityOrder = model.PriorityOrder; - if (model.RemovePhoto) await RemovePersonPhoto(sc); - else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, sc, model.ID); - await SteeringCommitteeDB.EditSteeringCommittee(_context, sc); - break; - } - return RedirectToAction(nameof(Index), new { type = model.Type }); - } - return View(model); - } - - [HttpGet] - public async Task Delete(int id, PersonType type) - { - PersonViewModel? model = type switch - { - PersonType.Staff => await StaffDB.GetStaffMember(_context, id) is Staff s - ? PersonViewModel.FromStaff(s) : null, - PersonType.Board => await BoardDB.GetBoardMember(_context, id) is Board b - ? PersonViewModel.FromBoard(b) : null, - PersonType.SteeringCommittee => await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id) is SteeringCommittee sc - ? PersonViewModel.FromSteeringCommittee(sc) : null, - _ => null - }; + if (model == null) return NotFound(); + return View(model); + } - if (model == null) return NotFound(); - return View(model); - } + [HttpPost] + public async Task Edit(PersonViewModel model) + { + ValidateTypeSpecificFields(model); - [HttpPost, ActionName("Delete")] - public async Task ConfirmDelete(int id, PersonType type) + if (ModelState.IsValid) { - switch (type) + switch (model.Type) { case PersonType.Staff: - var staff = await StaffDB.GetStaffMember(_context, id); + var staff = await StaffDB.GetStaffMember(_context, model.ID); if (staff == null) return NotFound(); - await RemovePersonPhoto(staff); - await StaffDB.Delete(_context, staff); + staff.Name = model.Name; + staff.Title = model.Title; + staff.Phone = model.Phone; + staff.Extension = model.Extension; + staff.Email = model.Email!; + staff.PriorityOrder = model.PriorityOrder; + if (model.RemovePhoto) await RemovePersonPhoto(staff); + else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, staff, model.ID); + await StaffDB.SaveChanges(_context, staff); break; case PersonType.Board: - var board = await BoardDB.GetBoardMember(_context, id); + var board = await BoardDB.GetBoardMember(_context, model.ID); if (board == null) return NotFound(); - await RemovePersonPhoto(board); - await BoardDB.Delete(_context, board); + board.Name = model.Name; + board.Title = model.Title; + board.MembershipStart = model.MembershipStart!; + board.PriorityOrder = model.PriorityOrder; + if (model.RemovePhoto) await RemovePersonPhoto(board); + else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, board, model.ID); + await BoardDB.EditBoardMember(_context, board); break; case PersonType.SteeringCommittee: - var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id); + var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, model.ID); if (sc == null) return NotFound(); - await RemovePersonPhoto(sc); - await SteeringCommitteeDB.Delete(_context, sc); + sc.Name = model.Name; + sc.Title = model.Title; + sc.PriorityOrder = model.PriorityOrder; + if (model.RemovePhoto) await RemovePersonPhoto(sc); + else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, sc, model.ID); + await SteeringCommitteeDB.EditSteeringCommittee(_context, sc); break; } - return RedirectToAction(nameof(Index), new { type }); + return RedirectToAction(nameof(Index), new { type = model.Type }); } + return View(model); + } - private void ValidateTypeSpecificFields(PersonViewModel model) + [HttpGet] + public async Task Delete(int id, PersonType type) + { + PersonViewModel? model = type switch { - if (model.Type == PersonType.Staff && string.IsNullOrWhiteSpace(model.Email)) - ModelState.AddModelError(nameof(PersonViewModel.Email), "Email is required for staff members."); - if (model.Type == PersonType.Board && string.IsNullOrWhiteSpace(model.MembershipStart)) - ModelState.AddModelError(nameof(PersonViewModel.MembershipStart), "Membership start year is required."); - } + PersonType.Staff => await StaffDB.GetStaffMember(_context, id) is Staff s + ? PersonViewModel.FromStaff(s) : null, + PersonType.Board => await BoardDB.GetBoardMember(_context, id) is Board b + ? PersonViewModel.FromBoard(b) : null, + PersonType.SteeringCommittee => await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id) is SteeringCommittee sc + ? PersonViewModel.FromSteeringCommittee(sc) : null, + _ => null + }; - private async Task HandlePhotoUpload(IFormFile? photoFile, People person, int? personId = null) - { - if (photoFile == null || photoFile.Length == 0) return; + if (model == null) return NotFound(); + return View(model); + } - try - { - if (!ImageService.IsValidImageFile(photoFile)) - throw new InvalidOperationException("Please upload a valid image file (JPEG, PNG, GIF, or BMP)."); + [HttpPost, ActionName("Delete")] + public async Task ConfirmDelete(int id, PersonType type) + { + switch (type) + { + case PersonType.Staff: + var staff = await StaffDB.GetStaffMember(_context, id); + if (staff == null) return NotFound(); + await RemovePersonPhoto(staff); + await StaffDB.Delete(_context, staff); + break; - if (personId.HasValue && !string.IsNullOrEmpty(person.ImageUrl)) - await RemovePersonPhoto(person); + case PersonType.Board: + var board = await BoardDB.GetBoardMember(_context, id); + if (board == null) return NotFound(); + await RemovePersonPhoto(board); + await BoardDB.Delete(_context, board); + break; - var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, personId ?? 0); - using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream(), 350, 350); - var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); - person.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error handling image upload."); - } + case PersonType.SteeringCommittee: + var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id); + if (sc == null) return NotFound(); + await RemovePersonPhoto(sc); + await SteeringCommitteeDB.Delete(_context, sc); + break; } + return RedirectToAction(nameof(Index), new { type }); + } + + private void ValidateTypeSpecificFields(PersonViewModel model) + { + if (model.Type == PersonType.Staff && string.IsNullOrWhiteSpace(model.Email)) + ModelState.AddModelError(nameof(PersonViewModel.Email), "Email is required for staff members."); + if (model.Type == PersonType.Board && string.IsNullOrWhiteSpace(model.MembershipStart)) + ModelState.AddModelError(nameof(PersonViewModel.MembershipStart), "Membership start year is required."); + } - private async Task RemovePersonPhoto(People person) + private async Task HandlePhotoUpload(IFormFile? photoFile, People person, int? personId = null) + { + if (photoFile == null || photoFile.Length == 0) return; + + try { - if (string.IsNullOrEmpty(person.ImageUrl)) return; + if (!ImageService.IsValidImageFile(photoFile)) + throw new InvalidOperationException("Please upload a valid image file (JPEG, PNG, GIF, or BMP)."); - try - { - var fileName = person.ImageUrl.Split('/').LastOrDefault(); - if (!string.IsNullOrEmpty(fileName)) - await _azureBlobUploader.DeleteFileAsync(fileName); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deleting photo."); - } + if (personId.HasValue && !string.IsNullOrEmpty(person.ImageUrl)) + await RemovePersonPhoto(person); - person.ImageUrl = null; + var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, personId ?? 0); + using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream(), 350, 350); + var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); + person.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error handling image upload."); } } + + private async Task RemovePersonPhoto(People person) + { + if (string.IsNullOrEmpty(person.ImageUrl)) return; + + try + { + var fileName = person.ImageUrl.Split('/').LastOrDefault(); + if (!string.IsNullOrEmpty(fileName)) + await _azureBlobUploader.DeleteFileAsync(fileName); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting photo."); + } + + person.ImageUrl = null; + } } diff --git a/PC2/Models/FormFileFromStream.cs b/PC2/Models/FormFileFromStream.cs index cff336d3..17c7d35e 100644 --- a/PC2/Models/FormFileFromStream.cs +++ b/PC2/Models/FormFileFromStream.cs @@ -1,43 +1,42 @@ -namespace PC2.Models +namespace PC2.Models; + +/// +/// Implementation of IFormFile that wraps a Stream +/// Used for creating IFormFile from resized image streams +/// +public class FormFileFromStream : IFormFile { - /// - /// Implementation of IFormFile that wraps a Stream - /// Used for creating IFormFile from resized image streams - /// - public class FormFileFromStream : IFormFile - { - private readonly Stream _stream; - private readonly string _name; - private readonly string _contentType; + private readonly Stream _stream; + private readonly string _name; + private readonly string _contentType; - public FormFileFromStream(Stream stream, string name, string contentType) - { - _stream = stream; - _name = name; - _contentType = contentType; - } + public FormFileFromStream(Stream stream, string name, string contentType) + { + _stream = stream; + _name = name; + _contentType = contentType; + } - public string ContentType => _contentType; - public string ContentDisposition => $"form-data; name=\"{Name}\"; filename=\"{FileName}\""; - public IHeaderDictionary Headers => new HeaderDictionary(); - public long Length => _stream.Length; - public string Name => _name; - public string FileName => _name; + public string ContentType => _contentType; + public string ContentDisposition => $"form-data; name=\"{Name}\"; filename=\"{FileName}\""; + public IHeaderDictionary Headers => new HeaderDictionary(); + public long Length => _stream.Length; + public string Name => _name; + public string FileName => _name; - public void CopyTo(Stream target) - { - _stream.CopyTo(target); - } + public void CopyTo(Stream target) + { + _stream.CopyTo(target); + } - public Task CopyToAsync(Stream target, CancellationToken cancellationToken = default) - { - return _stream.CopyToAsync(target, cancellationToken); - } + public Task CopyToAsync(Stream target, CancellationToken cancellationToken = default) + { + return _stream.CopyToAsync(target, cancellationToken); + } - public Stream OpenReadStream() - { - _stream.Position = 0; - return _stream; - } + public Stream OpenReadStream() + { + _stream.Position = 0; + return _stream; } } \ No newline at end of file diff --git a/PC2/Models/ViewModels/PersonViewModel.cs b/PC2/Models/ViewModels/PersonViewModel.cs index 1f504add..df7599f6 100644 --- a/PC2/Models/ViewModels/PersonViewModel.cs +++ b/PC2/Models/ViewModels/PersonViewModel.cs @@ -1,84 +1,83 @@ using System.ComponentModel.DataAnnotations; -namespace PC2.Models.ViewModels -{ - public class PersonViewModel - { - public int ID { get; set; } +namespace PC2.Models.ViewModels; - [Required] - public PersonType Type { get; set; } - - // ---- Common (People base) ---- +public class PersonViewModel +{ + public int ID { get; set; } - [Required] - public string Name { get; set; } = string.Empty; + [Required] + public PersonType Type { get; set; } - public string? Title { get; set; } + // ---- Common (People base) ---- - [Required] - [Display(Name = "Sort Priority")] - public byte PriorityOrder { get; set; } = 10; + [Required] + public string Name { get; set; } = string.Empty; - // ---- Photo ---- + public string? Title { get; set; } - public string? CurrentImageUrl { get; set; } + [Required] + [Display(Name = "Sort Priority")] + public byte PriorityOrder { get; set; } = 10; - [Display(Name = "Photo")] - public IFormFile? PhotoFile { get; set; } + // ---- Photo ---- - [Display(Name = "Remove current photo")] - public bool RemovePhoto { get; set; } + public string? CurrentImageUrl { get; set; } - // ---- Staff-specific ---- + [Display(Name = "Photo")] + public IFormFile? PhotoFile { get; set; } - public string? Phone { get; set; } + [Display(Name = "Remove current photo")] + public bool RemovePhoto { get; set; } - public int? Extension { get; set; } + // ---- Staff-specific ---- - [DataType(DataType.EmailAddress)] - [EmailAddress] - public string? Email { get; set; } + public string? Phone { get; set; } - // ---- Board-specific ---- + public int? Extension { get; set; } - [Display(Name = "Membership Start Year")] - public string? MembershipStart { get; set; } + [DataType(DataType.EmailAddress)] + [EmailAddress] + public string? Email { get; set; } - // ---- Factory methods ---- + // ---- Board-specific ---- - public static PersonViewModel FromStaff(Staff s) => new() - { - ID = s.ID, - Type = PersonType.Staff, - Name = s.Name, - Title = s.Title, - PriorityOrder = s.PriorityOrder, - CurrentImageUrl = s.ImageUrl, - Phone = s.Phone, - Extension = s.Extension, - Email = s.Email - }; + [Display(Name = "Membership Start Year")] + public string? MembershipStart { get; set; } - public static PersonViewModel FromBoard(Board b) => new() - { - ID = b.ID, - Type = PersonType.Board, - Name = b.Name, - Title = b.Title, - PriorityOrder = b.PriorityOrder, - CurrentImageUrl = b.ImageUrl, - MembershipStart = b.MembershipStart - }; + // ---- Factory methods ---- - public static PersonViewModel FromSteeringCommittee(SteeringCommittee sc) => new() - { - ID = sc.ID, - Type = PersonType.SteeringCommittee, - Name = sc.Name, - Title = sc.Title, - PriorityOrder = sc.PriorityOrder, - CurrentImageUrl = sc.ImageUrl - }; - } + public static PersonViewModel FromStaff(Staff s) => new() + { + ID = s.ID, + Type = PersonType.Staff, + Name = s.Name, + Title = s.Title, + PriorityOrder = s.PriorityOrder, + CurrentImageUrl = s.ImageUrl, + Phone = s.Phone, + Extension = s.Extension, + Email = s.Email + }; + + public static PersonViewModel FromBoard(Board b) => new() + { + ID = b.ID, + Type = PersonType.Board, + Name = b.Name, + Title = b.Title, + PriorityOrder = b.PriorityOrder, + CurrentImageUrl = b.ImageUrl, + MembershipStart = b.MembershipStart + }; + + public static PersonViewModel FromSteeringCommittee(SteeringCommittee sc) => new() + { + ID = sc.ID, + Type = PersonType.SteeringCommittee, + Name = sc.Name, + Title = sc.Title, + PriorityOrder = sc.PriorityOrder, + CurrentImageUrl = sc.ImageUrl + }; } diff --git a/PC2/Views/Home/About.cshtml b/PC2/Views/Home/About.cshtml index 96e3af30..e86d4146 100644 --- a/PC2/Views/Home/About.cshtml +++ b/PC2/Views/Home/About.cshtml @@ -241,11 +241,5 @@
    } - } -
    - - - - } \ No newline at end of file From ac9129fe2e78560afb20d70a65702701c9caba90 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Tue, 3 Mar 2026 13:28:33 -0800 Subject: [PATCH 17/19] Refactor: let caller manage file stream disposal Removed using statement around file stream in AzureBlobUploader. The method no longer disposes the stream after upload; instead, the caller is now responsible for managing the stream's lifetime. This change clarifies ownership and prevents premature disposal. --- PC2/Models/AzureBlobUploader.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/PC2/Models/AzureBlobUploader.cs b/PC2/Models/AzureBlobUploader.cs index a0062f86..29b361b6 100644 --- a/PC2/Models/AzureBlobUploader.cs +++ b/PC2/Models/AzureBlobUploader.cs @@ -40,10 +40,9 @@ public async Task UploadFileAsync(IFormFile file, string blobName) var blobClient = containerClient.GetBlobClient(blobName); - using (var stream = file.OpenReadStream()) - { - await blobClient.UploadAsync(stream, overwrite: true); - } + // UploadFileAsync - caller owns the stream lifetime + var stream = file.OpenReadStream(); + await blobClient.UploadAsync(stream, overwrite: true); return blobClient.Uri.ToString(); } From 1111bd5f695dada8c63f18c7c047f02ce6b0fd45 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Tue, 3 Mar 2026 14:03:05 -0800 Subject: [PATCH 18/19] Add error handling to PeopleController actions Wrap Create, Edit, and Delete POST actions in try-catch blocks to log exceptions and display user-friendly error messages. Simplify HandlePhotoUpload by removing its internal try-catch, allowing errors to propagate. Ensure RemovePersonPhoto logs and rethrows exceptions. These changes improve reliability and user feedback. --- PC2/Controllers/PeopleController.cs | 231 +++++++++++++++------------- 1 file changed, 125 insertions(+), 106 deletions(-) diff --git a/PC2/Controllers/PeopleController.cs b/PC2/Controllers/PeopleController.cs index 08904e42..c717d936 100644 --- a/PC2/Controllers/PeopleController.cs +++ b/PC2/Controllers/PeopleController.cs @@ -49,46 +49,54 @@ public async Task Create(PersonViewModel model) if (ModelState.IsValid) { - switch (model.Type) + try { - case PersonType.Staff: - var staff = new Staff - { - Name = model.Name, - Title = model.Title, - Phone = model.Phone, - Extension = model.Extension, - Email = model.Email!, - PriorityOrder = model.PriorityOrder - }; - await HandlePhotoUpload(model.PhotoFile, staff); - await StaffDB.AddStaff(_context, staff); - break; + switch (model.Type) + { + case PersonType.Staff: + var staff = new Staff + { + Name = model.Name, + Title = model.Title, + Phone = model.Phone, + Extension = model.Extension, + Email = model.Email!, + PriorityOrder = model.PriorityOrder + }; + await HandlePhotoUpload(model.PhotoFile, staff); + await StaffDB.AddStaff(_context, staff); + break; - case PersonType.Board: - var board = new Board - { - Name = model.Name, - Title = model.Title, - MembershipStart = model.MembershipStart!, - PriorityOrder = model.PriorityOrder - }; - await HandlePhotoUpload(model.PhotoFile, board); - await BoardDB.CreateBoardMember(_context, board); - break; + case PersonType.Board: + var board = new Board + { + Name = model.Name, + Title = model.Title, + MembershipStart = model.MembershipStart!, + PriorityOrder = model.PriorityOrder + }; + await HandlePhotoUpload(model.PhotoFile, board); + await BoardDB.CreateBoardMember(_context, board); + break; - case PersonType.SteeringCommittee: - var sc = new SteeringCommittee - { - Name = model.Name, - Title = model.Title, - PriorityOrder = model.PriorityOrder - }; - await HandlePhotoUpload(model.PhotoFile, sc); - await SteeringCommitteeDB.Create(_context, sc); - break; + case PersonType.SteeringCommittee: + var sc = new SteeringCommittee + { + Name = model.Name, + Title = model.Title, + PriorityOrder = model.PriorityOrder + }; + await HandlePhotoUpload(model.PhotoFile, sc); + await SteeringCommitteeDB.Create(_context, sc); + break; + } + return RedirectToAction(nameof(Index), new { type = model.Type }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error creating person record."); + TempData["Message"] = "There was a problem creating this record. Please try again later."; } - return RedirectToAction(nameof(Index), new { type = model.Type }); } return View(model); } @@ -118,46 +126,54 @@ public async Task Edit(PersonViewModel model) if (ModelState.IsValid) { - switch (model.Type) + try { - case PersonType.Staff: - var staff = await StaffDB.GetStaffMember(_context, model.ID); - if (staff == null) return NotFound(); - staff.Name = model.Name; - staff.Title = model.Title; - staff.Phone = model.Phone; - staff.Extension = model.Extension; - staff.Email = model.Email!; - staff.PriorityOrder = model.PriorityOrder; - if (model.RemovePhoto) await RemovePersonPhoto(staff); - else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, staff, model.ID); - await StaffDB.SaveChanges(_context, staff); - break; + switch (model.Type) + { + case PersonType.Staff: + var staff = await StaffDB.GetStaffMember(_context, model.ID); + if (staff == null) return NotFound(); + staff.Name = model.Name; + staff.Title = model.Title; + staff.Phone = model.Phone; + staff.Extension = model.Extension; + staff.Email = model.Email!; + staff.PriorityOrder = model.PriorityOrder; + if (model.RemovePhoto) await RemovePersonPhoto(staff); + else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, staff, model.ID); + await StaffDB.SaveChanges(_context, staff); + break; - case PersonType.Board: - var board = await BoardDB.GetBoardMember(_context, model.ID); - if (board == null) return NotFound(); - board.Name = model.Name; - board.Title = model.Title; - board.MembershipStart = model.MembershipStart!; - board.PriorityOrder = model.PriorityOrder; - if (model.RemovePhoto) await RemovePersonPhoto(board); - else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, board, model.ID); - await BoardDB.EditBoardMember(_context, board); - break; + case PersonType.Board: + var board = await BoardDB.GetBoardMember(_context, model.ID); + if (board == null) return NotFound(); + board.Name = model.Name; + board.Title = model.Title; + board.MembershipStart = model.MembershipStart!; + board.PriorityOrder = model.PriorityOrder; + if (model.RemovePhoto) await RemovePersonPhoto(board); + else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, board, model.ID); + await BoardDB.EditBoardMember(_context, board); + break; - case PersonType.SteeringCommittee: - var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, model.ID); - if (sc == null) return NotFound(); - sc.Name = model.Name; - sc.Title = model.Title; - sc.PriorityOrder = model.PriorityOrder; - if (model.RemovePhoto) await RemovePersonPhoto(sc); - else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, sc, model.ID); - await SteeringCommitteeDB.EditSteeringCommittee(_context, sc); - break; + case PersonType.SteeringCommittee: + var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, model.ID); + if (sc == null) return NotFound(); + sc.Name = model.Name; + sc.Title = model.Title; + sc.PriorityOrder = model.PriorityOrder; + if (model.RemovePhoto) await RemovePersonPhoto(sc); + else if (model.PhotoFile != null) await HandlePhotoUpload(model.PhotoFile, sc, model.ID); + await SteeringCommitteeDB.EditSteeringCommittee(_context, sc); + break; + } + return RedirectToAction(nameof(Index), new { type = model.Type }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error editing person record."); + TempData["Message"] = "There was a problem saving this record. Please try again later."; } - return RedirectToAction(nameof(Index), new { type = model.Type }); } return View(model); } @@ -183,29 +199,38 @@ public async Task Delete(int id, PersonType type) [HttpPost, ActionName("Delete")] public async Task ConfirmDelete(int id, PersonType type) { - switch (type) + try { - case PersonType.Staff: - var staff = await StaffDB.GetStaffMember(_context, id); - if (staff == null) return NotFound(); - await RemovePersonPhoto(staff); - await StaffDB.Delete(_context, staff); - break; + switch (type) + { + case PersonType.Staff: + var staff = await StaffDB.GetStaffMember(_context, id); + if (staff == null) return NotFound(); + await RemovePersonPhoto(staff); + await StaffDB.Delete(_context, staff); + break; - case PersonType.Board: - var board = await BoardDB.GetBoardMember(_context, id); - if (board == null) return NotFound(); - await RemovePersonPhoto(board); - await BoardDB.Delete(_context, board); - break; + case PersonType.Board: + var board = await BoardDB.GetBoardMember(_context, id); + if (board == null) return NotFound(); + await RemovePersonPhoto(board); + await BoardDB.Delete(_context, board); + break; - case PersonType.SteeringCommittee: - var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id); - if (sc == null) return NotFound(); - await RemovePersonPhoto(sc); - await SteeringCommitteeDB.Delete(_context, sc); - break; + case PersonType.SteeringCommittee: + var sc = await SteeringCommitteeDB.GetSteeringCommitteeMember(_context, id); + if (sc == null) return NotFound(); + await RemovePersonPhoto(sc); + await SteeringCommitteeDB.Delete(_context, sc); + break; + } } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting person record."); + TempData["Message"] = "There was a problem deleting this record. Please try again later."; + } + return RedirectToAction(nameof(Index), new { type }); } @@ -221,23 +246,16 @@ private async Task HandlePhotoUpload(IFormFile? photoFile, People person, int? p { if (photoFile == null || photoFile.Length == 0) return; - try - { - if (!ImageService.IsValidImageFile(photoFile)) - throw new InvalidOperationException("Please upload a valid image file (JPEG, PNG, GIF, or BMP)."); + if (!ImageService.IsValidImageFile(photoFile)) + throw new InvalidOperationException("Please upload a valid image file (JPEG, PNG, GIF, or BMP)."); - if (personId.HasValue && !string.IsNullOrEmpty(person.ImageUrl)) - await RemovePersonPhoto(person); + if (personId.HasValue && !string.IsNullOrEmpty(person.ImageUrl)) + await RemovePersonPhoto(person); - var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, personId ?? 0); - using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream(), 350, 350); - var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); - person.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error handling image upload."); - } + var safeFileName = ImageService.GetSafeImageFileName(photoFile.FileName, personId ?? 0); + using var resizedImageStream = await _imageService.ResizeImageAsync(photoFile.OpenReadStream(), 350, 350); + var resizedFormFile = new FormFileFromStream(resizedImageStream, safeFileName, photoFile.ContentType); + person.ImageUrl = await _azureBlobUploader.UploadFileAsync(resizedFormFile, safeFileName); } private async Task RemovePersonPhoto(People person) @@ -253,6 +271,7 @@ private async Task RemovePersonPhoto(People person) catch (Exception ex) { _logger.LogError(ex, "Error deleting photo."); + throw; } person.ImageUrl = null; From f715c1d17e48c895fa7da0928d4a81526b4309b7 Mon Sep 17 00:00:00 2001 From: JoeProgrammer88 Date: Tue, 3 Mar 2026 14:08:34 -0800 Subject: [PATCH 19/19] Update staff images to use rounded corners, not circles Changed the staff image class from 'rounded-circle' to 'rounded' in About.cshtml, so images now have slightly rounded corners instead of being fully circular. This improves visual consistency with other image styles on the site. --- PC2/Views/Home/About.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PC2/Views/Home/About.cshtml b/PC2/Views/Home/About.cshtml index 3e664a60..0e10291a 100644 --- a/PC2/Views/Home/About.cshtml +++ b/PC2/Views/Home/About.cshtml @@ -59,7 +59,7 @@
    - @(string.IsNullOrEmpty(staff.Title) ? staff.Name : staff.Name + + @(string.IsNullOrEmpty(staff.Title) ? staff.Name : staff.Name +
    @staff.Name
    @if (!string.IsNullOrEmpty(staff.Title)) {