diff --git a/PC2/Controllers/ResourcesController.cs b/PC2/Controllers/ResourcesController.cs index 97a4d408..67c154c6 100644 --- a/PC2/Controllers/ResourcesController.cs +++ b/PC2/Controllers/ResourcesController.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Mvc; using PC2.Data; using PC2.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; namespace PC2.Controllers { @@ -29,15 +31,21 @@ public IActionResult Index() public async Task ResourceGuide(int categoryID) { ResourceGuideModel resourceGuide = new ResourceGuideModel(); + + ViewData["ShowFeedbackForm"] = false; + if (categoryID != 0) { resourceGuide.Agencies = await AgencyDB.GetSpecificAgenciesAsync(_context, categoryID); resourceGuide.Category = await AgencyCategoryDB.GetAgencyCategory(_context, categoryID); TrackResourceGuideTelemetry("Manual/Category", resourceGuide.Category.AgencyCategoryName); + + ViewData["ShowFeedbackForm"] = true; } await AgencyDB.GetDataForDataLists(_context, resourceGuide); + return View(resourceGuide); } @@ -69,12 +77,15 @@ public async Task ResourceGuide(ResourceGuideModel searchModel) UserSearchedByAgency = searchModel.UserSearchedByAgency }; + ViewBag.ShowFeedbackForm = false; + if (!string.IsNullOrEmpty(searchModel.UserSearchedByAgency)) { if (searchModel.SearchedAgency != null) { TrackResourceGuideTelemetry("Agency", searchModel.SearchedAgency); resourceGuide.Agencies = await AgencyDB.GetAgenciesByName(_context, searchModel.SearchedAgency); + ViewData["ShowFeedbackForm"] = true; } } else if (!string.IsNullOrEmpty(searchModel.UserSearchedByCityOrService)) @@ -87,18 +98,21 @@ public async Task ResourceGuide(ResourceGuideModel searchModel) searchModel.SearchedCategory, searchModel.SearchedCity); resourceGuide.CurrentCity = searchModel.SearchedCity; resourceGuide.Category = await AgencyCategoryDB.GetAgencyCategory(_context, searchModel.SearchedCategory); + ViewData["ShowFeedbackForm"] = true; } else if (searchModel.SearchedCategory != null) { TrackResourceGuideTelemetry("Service", $"{searchModel.SearchedCategory}"); resourceGuide.Category = await AgencyCategoryDB.GetAgencyCategory(_context, searchModel.SearchedCategory); resourceGuide.Agencies = await AgencyDB.GetSpecificAgenciesAsync(_context, resourceGuide.Category.AgencyCategoryId); + ViewData["ShowFeedbackForm"] = true; } else if (searchModel.SearchedCity != null) { TrackResourceGuideTelemetry("City", searchModel.SearchedCity); resourceGuide.CurrentCity = searchModel.SearchedCity; resourceGuide.Agencies = await AgencyDB.GetSpecificAgenciesAsync(_context, searchModel.SearchedCity); + ViewData["ShowFeedbackForm"] = true; } } @@ -148,5 +162,205 @@ public IActionResult VirtualCloset() return View(newsletterFiles); } + + /// + /// Submits user feedback. + /// + /// The feedback model containing user input. + /// A redirect to the ResourceGuide action on success, + /// or the ResourceGuide view if the model is invalid. + [HttpPost] + public async Task SubmitFeedback(Feedback model) + { + if (ModelState.IsValid) + { + model.SubmittedAt = DateTime.UtcNow; + _context.Feedback.Add(model); + await _context.SaveChangesAsync(); + + TempData["SuccessMessage"] = "Thank you for your feedback!"; + return RedirectToAction("ResourceGuide"); + } + + return RedirectToAction("ResourceGuide"); + } + + /// + /// Allows administrators to view feedback submitted by users. + /// This method retrieves and displays all feedback entries from the database, ordered by submission date. + /// + /// + /// A view displaying the list of feedback, including their comments and submission dates. + /// + [Authorize(Roles = IdentityHelper.Admin)] + [HttpGet] + public async Task ViewFeedback() + { + List feedbackList = await _context.Feedback + .OrderByDescending(f => f.SubmittedAt) + .Select(f => new FeedbackViewModel + { + Id = f.Id, + IsResourceFound = f.IsResourceFound ? "Yes" : "No", + Comments = f.Comments, + FormattedSubmittedAt = f.SubmittedAt.ToString("yyyy-MM-dd HH:mm"), + IsReviewed = f.IsReviewed + }) + .ToListAsync(); + + return View(feedbackList); + } + + /// + /// Allows administrators to view and edit feedback submitted by users. + /// This method retrieves the feedback based on its ID and prepares it for editing. + /// + /// The ID of the feedback to edit. + /// + /// A view displaying the feedback details, ready for editing, or a 404 error if the feedback does not exist. + /// + [Authorize(Roles = IdentityHelper.Admin)] + [HttpGet] + public async Task EditFeedback(int id) + { + Feedback? feedback = await _context.Feedback.FindAsync(id); + if (feedback == null) + { + return NotFound(); + } + + FeedbackViewModel viewModel = new() + { + Id = feedback.Id, + IsResourceFound = feedback.IsResourceFound ? "Yes" : "No", + Comments = feedback.Comments, + FormattedSubmittedAt = feedback.SubmittedAt.ToString("yyyy-MM-dd HH:mm"), + IsReviewed = feedback.IsReviewed + }; + + return View(viewModel); + } + + /// + /// Updates the feedback submitted by a user. + /// This method processes the edits made by the administrator and updates the feedback in the database. + /// + /// The feedback view model containing the updated data. + /// + /// A redirect to the "ViewFeedback" action if the feedback is successfully updated. + /// A view with validation errors if the model is invalid. + /// + [Authorize(Roles = IdentityHelper.Admin)] + [HttpPost] + public async Task EditFeedback(FeedbackViewModel model) + { + if (!ModelState.IsValid) + { + return View(model); + } + + Feedback? feedback = await _context.Feedback.FindAsync(model.Id); + if (feedback == null) + { + return NotFound(); + } + + feedback.Comments = model.Comments; + feedback.IsReviewed = model.IsReviewed; + + _context.Feedback.Update(feedback); + await _context.SaveChangesAsync(); + + return RedirectToAction(nameof(ViewFeedback)); + } + + /// + /// Allows administrators to view the confirmation page for deleting feedback. + /// This method retrieves the feedback based on its ID and presents a confirmation page to the user. + /// + /// The ID of the feedback to delete. + /// + /// A view displaying the feedback to confirm deletion, or a 404 error if the feedback does not exist. + /// + [Authorize(Roles = IdentityHelper.Admin)] + [HttpGet] + public async Task DeleteFeedback(int id) + { + Feedback? feedback = await _context.Feedback.FindAsync(id); + if (feedback == null) + { + return NotFound(); + } + + return View(feedback); + } + + /// + /// Deletes the specified feedback from the database. + /// This method removes the feedback entry from the database after confirmation. + /// + /// The ID of the feedback to delete. + /// + /// A redirect to the "ViewFeedback" action after the feedback has been deleted. + /// + [Authorize(Roles = IdentityHelper.Admin)] + [HttpPost, ActionName("DeleteFeedback")] + public async Task DeleteConfirmed(int id) + { + Feedback? feedback = await _context.Feedback.FindAsync(id); + if (feedback == null) + { + return NotFound(); + } + + _context.Feedback.Remove(feedback); + await _context.SaveChangesAsync(); + + return RedirectToAction(nameof(ViewFeedback)); + } + + /// + /// Displays a view for marking a feedback entry as reviewed. + /// + /// The ID of the feedback entry to mark as reviewed. + /// The MarkAsReviewed view with the feedback data. + [HttpGet] + public async Task MarkAsReviewed(int? id) + { + if (id == null) + { + return NotFound(); + } + + Feedback? feedback = await _context.Feedback.FindAsync(id); + if (feedback == null) + { + return NotFound(); + } + + return View(feedback); + } + + /// + /// Marks a feedback entry as reviewed. + /// + /// The ID of the feedback entry to mark as reviewed. + /// A redirect to the ViewFeedback action. + [Authorize(Roles = IdentityHelper.Admin)] + [HttpPost] + public async Task MarkAsReviewed(int id) + { + Feedback? feedback = await _context.Feedback.FindAsync(id); + if (feedback == null) + { + return NotFound(); + } + + feedback.IsReviewed = true; + await _context.SaveChangesAsync(); + + return RedirectToAction("ViewFeedback"); + } + } } diff --git a/PC2/Data/ApplicationDbContext.cs b/PC2/Data/ApplicationDbContext.cs index bddbf5ae..7f13e807 100644 --- a/PC2/Data/ApplicationDbContext.cs +++ b/PC2/Data/ApplicationDbContext.cs @@ -42,6 +42,7 @@ protected override void ConfigureConventions(ModelConfigurationBuilder builder) public virtual DbSet NewsletterFile { get; set; } public virtual DbSet HousingProgram { get; set; } + public virtual DbSet Feedback { get; set; } } internal class DateOnlyConverter : ValueConverter diff --git a/PC2/Data/Migrations/20250130011311_AddFeedbackTable.Designer.cs b/PC2/Data/Migrations/20250130011311_AddFeedbackTable.Designer.cs new file mode 100644 index 00000000..a9a03ce3 --- /dev/null +++ b/PC2/Data/Migrations/20250130011311_AddFeedbackTable.Designer.cs @@ -0,0 +1,557 @@ +// +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("20250130011311_AddFeedbackTable")] + partial class AddFeedbackTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .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.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Comments") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FoundResource") + .HasColumnType("bit"); + + b.Property("SubmittedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Feedback"); + }); + + 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("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/20250130011311_AddFeedbackTable.cs b/PC2/Data/Migrations/20250130011311_AddFeedbackTable.cs new file mode 100644 index 00000000..a5989ac0 --- /dev/null +++ b/PC2/Data/Migrations/20250130011311_AddFeedbackTable.cs @@ -0,0 +1,55 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PC2.Data.Migrations +{ + /// + public partial class AddFeedbackTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Discriminator", + table: "People", + type: "nvarchar(21)", + maxLength: 21, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.CreateTable( + name: "Feedback", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + FoundResource = table.Column(type: "bit", nullable: false), + Comments = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + SubmittedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Feedback", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Feedback"); + + migrationBuilder.AlterColumn( + name: "Discriminator", + table: "People", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(21)", + oldMaxLength: 21); + } + } +} diff --git a/PC2/Data/Migrations/20250216203424_RenameFoundResourceToIsResourceFound.Designer.cs b/PC2/Data/Migrations/20250216203424_RenameFoundResourceToIsResourceFound.Designer.cs new file mode 100644 index 00000000..b6fe42a3 --- /dev/null +++ b/PC2/Data/Migrations/20250216203424_RenameFoundResourceToIsResourceFound.Designer.cs @@ -0,0 +1,557 @@ +// +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("20250216203424_RenameFoundResourceToIsResourceFound")] + partial class RenameFoundResourceToIsResourceFound + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .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.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Comments") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsResourceFound") + .HasColumnType("bit"); + + b.Property("SubmittedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Feedback"); + }); + + 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("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/20250216203424_RenameFoundResourceToIsResourceFound.cs b/PC2/Data/Migrations/20250216203424_RenameFoundResourceToIsResourceFound.cs new file mode 100644 index 00000000..215a83ff --- /dev/null +++ b/PC2/Data/Migrations/20250216203424_RenameFoundResourceToIsResourceFound.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PC2.Data.Migrations +{ + /// + public partial class RenameFoundResourceToIsResourceFound : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "FoundResource", + table: "Feedback", + newName: "IsResourceFound"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "IsResourceFound", + table: "Feedback", + newName: "FoundResource"); + } + } +} diff --git a/PC2/Data/Migrations/20250217035356_AddIsReviewedToFeedback.Designer.cs b/PC2/Data/Migrations/20250217035356_AddIsReviewedToFeedback.Designer.cs new file mode 100644 index 00000000..06e9ace9 --- /dev/null +++ b/PC2/Data/Migrations/20250217035356_AddIsReviewedToFeedback.Designer.cs @@ -0,0 +1,560 @@ +// +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("20250217035356_AddIsReviewedToFeedback")] + partial class AddIsReviewedToFeedback + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .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.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Comments") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsResourceFound") + .HasColumnType("bit"); + + b.Property("IsReviewed") + .HasColumnType("bit"); + + b.Property("SubmittedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Feedback"); + }); + + 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("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/20250217035356_AddIsReviewedToFeedback.cs b/PC2/Data/Migrations/20250217035356_AddIsReviewedToFeedback.cs new file mode 100644 index 00000000..f093e630 --- /dev/null +++ b/PC2/Data/Migrations/20250217035356_AddIsReviewedToFeedback.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PC2.Data.Migrations +{ + /// + public partial class AddIsReviewedToFeedback : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsReviewed", + table: "Feedback", + type: "bit", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsReviewed", + table: "Feedback"); + } + } +} diff --git a/PC2/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/PC2/Data/Migrations/ApplicationDbContextModelSnapshot.cs index c875ee5b..038e418a 100644 --- a/PC2/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/PC2/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,10 +17,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("ProductVersion", "8.0.11") .HasAnnotation("Relational:MaxIdentifierLength", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity("AgencyAgencyCategory", b => { @@ -70,7 +70,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("ClaimType") .HasColumnType("nvarchar(max)"); @@ -160,7 +160,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); b.Property("ClaimType") .HasColumnType("nvarchar(max)"); @@ -245,7 +245,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("AgencyId"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("AgencyId")); b.Property("Address1") .HasColumnType("nvarchar(max)"); @@ -313,7 +313,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("AgencyCategoryId"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("AgencyCategoryId")); b.Property("AgencyCategoryName") .IsRequired() @@ -330,7 +330,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("CalendarEventID"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("CalendarEventID")); b.Property("CountyEvent") .HasColumnType("bit"); @@ -356,6 +356,32 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CalendarEvents"); }); + modelBuilder.Entity("PC2.Models.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Comments") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsResourceFound") + .HasColumnType("bit"); + + b.Property("IsReviewed") + .HasColumnType("bit"); + + b.Property("SubmittedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Feedback"); + }); + modelBuilder.Entity("PC2.Models.HousingProgram", b => { b.Property("HouseHoldSize") @@ -378,7 +404,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("NewsletterId"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("NewsletterId")); b.Property("Location") .IsRequired() @@ -399,11 +425,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("int"); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID"), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("ID")); b.Property("Discriminator") .IsRequired() - .HasColumnType("nvarchar(max)"); + .HasMaxLength(21) + .HasColumnType("nvarchar(21)"); b.Property("Name") .IsRequired() @@ -419,7 +446,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("People"); - b.HasDiscriminator("Discriminator").HasValue("People"); + b.HasDiscriminator().HasValue("People"); + + b.UseTphMappingStrategy(); }); modelBuilder.Entity("PC2.Models.Board", b => diff --git a/PC2/Models/Feedback.cs b/PC2/Models/Feedback.cs new file mode 100644 index 00000000..db316d63 --- /dev/null +++ b/PC2/Models/Feedback.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; + +namespace PC2.Models +{ + /// + /// Represents user feedback submitted on the website. + /// + public class Feedback + { + /// + /// Gets or sets the unique identifier for the feedback entry. + /// + public int Id { get; set; } + + /// + /// Gets or sets a value indicating whether the user found the resource they were looking for. + /// True represents "Yes", and False represents "No". + /// + public bool IsResourceFound { get; set; } + + /// + /// Gets or sets the optional comments provided by the user. + /// + [MaxLength(500)] + public string? Comments { get; set; } + + /// + /// Gets or sets the timestamp when the feedback was submitted. + /// + public DateTime SubmittedAt { get; set; } + + /// + /// Gets or sets a value indicating whether the feedback has been reviewed by an administrator. + /// + public bool IsReviewed { get; set; } = false; + } +} diff --git a/PC2/Models/FeedbackViewModel.cs b/PC2/Models/FeedbackViewModel.cs new file mode 100644 index 00000000..15591b0b --- /dev/null +++ b/PC2/Models/FeedbackViewModel.cs @@ -0,0 +1,37 @@ +namespace PC2.Models +{ + /// + /// ViewModel for displaying feedback information in the user interface. + /// + public class FeedbackViewModel + { + /// + /// Gets or sets the unique identifier for the feedback entry. + /// + public int Id { get; set; } + + /// + /// Gets or sets the status of whether the user found the resource. This is a required field. + /// This value is displayed as a string ("Yes" or "No") instead of a boolean. + /// + public required string IsResourceFound { get; set; } + + /// + /// Gets or sets the optional comments provided by the user in the feedback. + /// This value can be null if no comments were provided. + /// + public string? Comments { get; set; } + + /// + /// Gets or sets the formatted submission date for display purposes. + /// This ensures the date is shown in a user-friendly format. + /// This value is required for tracking and sorting purposes. + /// + public required string FormattedSubmittedAt { get; set; } + + /// + /// Gets or sets a value indicating whether the feedback has been reviewed by an administrator. + /// + public bool IsReviewed { get; set; } + } +} diff --git a/PC2/Views/Resources/DeleteFeedback.cshtml b/PC2/Views/Resources/DeleteFeedback.cshtml new file mode 100644 index 00000000..f9253166 --- /dev/null +++ b/PC2/Views/Resources/DeleteFeedback.cshtml @@ -0,0 +1,33 @@ +@model PC2.Models.Feedback + +@{ + ViewData["Title"] = "Delete Feedback"; +} + +

Delete Feedback

+ +

Are you sure you want to delete this feedback?

+ +
+
+
+
+ @Html.DisplayNameFor(model => model.Comments) +
+
+ @Html.DisplayFor(model => model.Comments) +
+
+ @Html.DisplayNameFor(model => model.SubmittedAt) +
+
+ @Html.DisplayFor(model => model.SubmittedAt) +
+
+ +
+ + + Back to List +
+
diff --git a/PC2/Views/Resources/EditFeedback.cshtml b/PC2/Views/Resources/EditFeedback.cshtml new file mode 100644 index 00000000..3f22542d --- /dev/null +++ b/PC2/Views/Resources/EditFeedback.cshtml @@ -0,0 +1,42 @@ +@model PC2.Models.FeedbackViewModel + +@{ + ViewData["Title"] = "Edit Feedback"; +} + +

Edit Feedback

+
+ +
+
+
+ + +
+ + + +
+ + + +
+ +
+ + + +
+ + +
+ + + Cancel + +
+
+ + diff --git a/PC2/Views/Resources/ResourceGuide.cshtml b/PC2/Views/Resources/ResourceGuide.cshtml index 6669db7f..90151233 100644 --- a/PC2/Views/Resources/ResourceGuide.cshtml +++ b/PC2/Views/Resources/ResourceGuide.cshtml @@ -1,5 +1,13 @@ @model PC2.Models.ResourceGuideModel +@if (TempData["SuccessMessage"] != null) +{ + +} + @section Head { } @@ -411,6 +419,26 @@ else
} +@if (ViewBag.ShowFeedbackForm != null && ViewBag.ShowFeedbackForm == true) +{ +
+
+ + + Maximum 500 characters. +
+ +
+ Did you find what you were looking for? +
+ +
+ + +
+
+} + @section Scripts{