From d1dfcdd2ba6295810c6a92105d4466efa9a40a74 Mon Sep 17 00:00:00 2001 From: Qaisyousuf Date: Fri, 8 Aug 2025 17:21:29 +0200 Subject: [PATCH] Implement condition logic for the questionnaire --- Model/Answer.cs | 2 + .../Controllers/QuestionnaireController.cs | 145 +- .../Admin/Views/Questionnaire/Create.cshtml | 100 -- .../Admin/Views/Questionnaire/Delete.cshtml | 2 +- .../Admin/Views/Questionnaire/Edit.cshtml | 5 +- .../Admin/Views/Questionnaire/Index.cshtml | 25 +- .../Admin/Views/Questionnaire/SetLogic.cshtml | 1281 +++++++++++++++++ Web/Areas/Admin/Views/_ViewImports.cshtml | 1 + ...6_AddConditionalLogicToAnswers.Designer.cs | 844 +++++++++++ ...0807114646_AddConditionalLogicToAnswers.cs | 60 + ...1628_AddConditionJsonToAnswers.Designer.cs | 835 +++++++++++ ...0250808101628_AddConditionJsonToAnswers.cs | 70 + Web/Migrations/SurveyContextModelSnapshot.cs | 3 + .../ConditionalLogicViewModel.cs | 54 + 14 files changed, 3297 insertions(+), 130 deletions(-) create mode 100644 Web/Areas/Admin/Views/Questionnaire/SetLogic.cshtml create mode 100644 Web/Migrations/20250807114646_AddConditionalLogicToAnswers.Designer.cs create mode 100644 Web/Migrations/20250807114646_AddConditionalLogicToAnswers.cs create mode 100644 Web/Migrations/20250808101628_AddConditionJsonToAnswers.Designer.cs create mode 100644 Web/Migrations/20250808101628_AddConditionJsonToAnswers.cs create mode 100644 Web/ViewModel/QuestionnaireVM/ConditionalLogicViewModel.cs diff --git a/Model/Answer.cs b/Model/Answer.cs index 034fc9c..2ce01b5 100644 --- a/Model/Answer.cs +++ b/Model/Answer.cs @@ -20,5 +20,7 @@ namespace Model public int QuestionId { get; set; } // Foreign key for Question [ForeignKey("QuestionId")] public Question? Question { get; set; } + + public string? ConditionJson { get; set; } } } diff --git a/Web/Areas/Admin/Controllers/QuestionnaireController.cs b/Web/Areas/Admin/Controllers/QuestionnaireController.cs index d2e8bd9..6cf121f 100644 --- a/Web/Areas/Admin/Controllers/QuestionnaireController.cs +++ b/Web/Areas/Admin/Controllers/QuestionnaireController.cs @@ -387,32 +387,29 @@ namespace Web.Areas.Admin.Controllers [ActionName("Delete")] public async Task DeleteConfirm(int id) { - - - try { - var deletedQuestionnaire = _questionnaire.Delete(id); + // Your Delete method is async Task, so you need to await it + // It doesn't return anything, so don't assign it to a variable + await _questionnaire.Delete(id); - - if (deletedQuestionnaire == null) - { - return NotFound(); // Or handle not found case appropriately - } - - // If deletion is successful, you can redirect to a success page or return a success message + // If we reach here, deletion was successful (no exception thrown) return Json(new { success = true, message = "Item deleted successfully" }); } - catch (Exception) + catch (ArgumentNullException ex) { - // Log the exception or handle it appropriately - return StatusCode(500, "An error occurred while processing your request."); + return Json(new { success = false, message = "Invalid ID provided" }); + } + catch (ArgumentException ex) + { + return Json(new { success = false, message = "Questionnaire not found" }); + } + catch (Exception ex) + { + // Log the actual exception to see what's wrong + System.Diagnostics.Debug.WriteLine($"Delete error: {ex.Message}"); + return Json(new { success = false, message = "An error occurred while deleting the questionnaire" }); } - - - - - } [HttpGet] @@ -667,7 +664,117 @@ namespace Web.Areas.Admin.Controllers return tokenWithExpiry; } + // Add these methods to your existing QuestionnaireController class + [HttpGet] + public IActionResult SetLogic(int id) + { + var questionnaire = _questionnaire.GetQuestionnaireWithQuestionAndAnswer(id); + + if (questionnaire == null) + { + return NotFound(); + } + + var viewModel = new SetLogicViewModel + { + QuestionnaireId = questionnaire.Id, + QuestionnaireName = questionnaire.Title ?? "Untitled Survey", + Questions = questionnaire.Questions.Select((q, index) => new QuestionLogicViewModel + { + QuestionId = q.Id, + QuestionText = q.Text ?? "", + QuestionType = q.Type, + QuestionNumber = index + 1, + Answers = q.Answers.Select(a => + { + var answerCondition = new AnswerConditionViewModel + { + AnswerId = a.Id, + AnswerText = a.Text ?? "" + }; + + // Parse existing condition if it exists + if (!string.IsNullOrEmpty(a.ConditionJson)) + { + try + { + var condition = System.Text.Json.JsonSerializer.Deserialize(a.ConditionJson); + if (condition != null) + { + answerCondition.ActionType = condition.ActionType; + answerCondition.TargetQuestionNumber = condition.TargetQuestionNumber; + answerCondition.SkipCount = condition.SkipCount; + answerCondition.EndMessage = condition.EndMessage; + } + } + catch (System.Text.Json.JsonException) + { + // If JSON is malformed, use default values + } + } + + return answerCondition; + }).ToList() + }).ToList() + }; + + // Pass total question count for dropdown options + ViewBag.TotalQuestions = questionnaire.Questions.Count; + + return View(viewModel); + } + + [HttpPost] + public async Task SaveLogic(SaveConditionsViewModel model) + { + if (!ModelState.IsValid) + { + TempData["Error"] = "Invalid data provided."; + return RedirectToAction(nameof(SetLogic), new { id = model.QuestionnaireId }); + } + + try + { + // Get questionnaire with answers + var questionnaire = _questionnaire.GetQuestionnaireWithQuestionAndAnswer(model.QuestionnaireId); + + if (questionnaire == null) + { + TempData["Error"] = "Questionnaire not found."; + return RedirectToAction(nameof(Index)); + } + + // Update each answer's condition + foreach (var conditionUpdate in model.Conditions) + { + var answer = questionnaire.Questions + .SelectMany(q => q.Answers) + .FirstOrDefault(a => a.Id == conditionUpdate.AnswerId); + + if (answer != null) + { + answer.ConditionJson = string.IsNullOrEmpty(conditionUpdate.ConditionJson) + ? null + : conditionUpdate.ConditionJson; + } + } + + // Save changes + await _questionnaire.Update(questionnaire); + await _questionnaire.commitAsync(); + + TempData["Success"] = "Conditional logic saved successfully!"; + return RedirectToAction(nameof(SetLogic), new { id = model.QuestionnaireId }); + } + catch (Exception ex) + { + TempData["Error"] = "An error occurred while saving conditions: " + ex.Message; + return RedirectToAction(nameof(SetLogic), new { id = model.QuestionnaireId }); + } + } } + + } diff --git a/Web/Areas/Admin/Views/Questionnaire/Create.cshtml b/Web/Areas/Admin/Views/Questionnaire/Create.cshtml index d2a9161..1e069ea 100644 --- a/Web/Areas/Admin/Views/Questionnaire/Create.cshtml +++ b/Web/Areas/Admin/Views/Questionnaire/Create.cshtml @@ -199,106 +199,6 @@ }); - @* - *@ } \ No newline at end of file diff --git a/Web/Areas/Admin/Views/Questionnaire/Delete.cshtml b/Web/Areas/Admin/Views/Questionnaire/Delete.cshtml index 13f826c..c7fd368 100644 --- a/Web/Areas/Admin/Views/Questionnaire/Delete.cshtml +++ b/Web/Areas/Admin/Views/Questionnaire/Delete.cshtml @@ -12,7 +12,7 @@ Are you sure you want to delete: - @Html.Raw(Model.Title.Length >= 30 ? Model.Title.Substring(0, 30) : Model.Title) + @Html.Raw(Model.Title.Length >= 100 ? Model.Title.Substring(0, 100) : Model.Title) diff --git a/Web/Areas/Admin/Views/Questionnaire/Edit.cshtml b/Web/Areas/Admin/Views/Questionnaire/Edit.cshtml index 0f84b74..ff34695 100644 --- a/Web/Areas/Admin/Views/Questionnaire/Edit.cshtml +++ b/Web/Areas/Admin/Views/Questionnaire/Edit.cshtml @@ -323,10 +323,7 @@ // Append the new question HTML to the container $('#questionsContainer').append(questionHtml); - // Show success message - // var successMessage = $(''); - // $('.question-title').prepend(successMessage); - // successMessage.fadeIn().delay(2000).fadeOut(); + }); diff --git a/Web/Areas/Admin/Views/Questionnaire/Index.cshtml b/Web/Areas/Admin/Views/Questionnaire/Index.cshtml index 7d1df4e..1caf056 100644 --- a/Web/Areas/Admin/Views/Questionnaire/Index.cshtml +++ b/Web/Areas/Admin/Views/Questionnaire/Index.cshtml @@ -64,13 +64,26 @@ } - + - Delete - Edit - Details - Send - + + + 🗑️ DELETE + + + ✏️ EDIT + + + 👁️ DETAILS + + + 📧 SEND + + + + + 🔀 SET LOGIC + } diff --git a/Web/Areas/Admin/Views/Questionnaire/SetLogic.cshtml b/Web/Areas/Admin/Views/Questionnaire/SetLogic.cshtml new file mode 100644 index 0000000..4169da4 --- /dev/null +++ b/Web/Areas/Admin/Views/Questionnaire/SetLogic.cshtml @@ -0,0 +1,1281 @@ +@model SetLogicViewModel + +@{ + ViewData["Title"] = "Set Conditional Logic"; +} + + + +
+
+ +
+
+
+

🔮 Conditional Logic Manager

+

Configure smart question routing for: @Model.QuestionnaireName

+
+
+ @Model.Questions.Count +
Questions
+
+
+
+ +
+ @if (TempData["Success"] != null) + { +
+ @TempData["Success"] +
+ } + + @if (TempData["Error"] != null) + { +
+ @TempData["Error"] +
+ } + + +
+

📋 Survey Structure Overview

+
+ @for (int i = 0; i < Model.Questions.Count; i++) + { + var hasConditions = Model.Questions[i].Answers.Any(a => a.ActionType != ConditionActionType.Continue); + +
+ @if (hasConditions) + { +
🔥 Logic
+ } +
Q@(i + 1)
+
+ @(Model.Questions[i].QuestionText.Length > 60 + ? Model.Questions[i].QuestionText.Substring(0, 60) + "..." + : Model.Questions[i].QuestionText) +
+
+ } +
+
+ +
+ + + +
+ @for (int questionIndex = 0; questionIndex < Model.Questions.Count; questionIndex++) + { + var question = Model.Questions[questionIndex]; + var hasConditions = question.Answers.Any(a => a.ActionType != ConditionActionType.Continue); + +
+
+

+ Question @(questionIndex + 1): @question.QuestionText +

+

+ @question.QuestionType • @question.Answers.Count answers + @if (hasConditions) + { + + Has Logic Rules + + } +

+
+ + +
+
+ +
+ @if (question.Answers.Any()) + { +
+ @for (int answerIndex = 0; answerIndex < question.Answers.Count; answerIndex++) + { + var answer = question.Answers[answerIndex]; + var hasCondition = answer.ActionType != ConditionActionType.Continue; + +
+
+

📝 Answer @(answerIndex + 1)

+ @if (hasCondition) + { + + Active + + } +
+
+
@answer.AnswerText
+ + +
+ + +
+ + + @{ + var skipToDisplay = answer.ActionType == ConditionActionType.SkipToQuestion ? "block" : "none"; + } +
+
+ + +
+
+ + + @{ + var skipCountDisplay = answer.ActionType == ConditionActionType.SkipCount ? "block" : "none"; + var maxSkip = Math.Max(1, ViewBag.TotalQuestions - questionIndex - 1); + var skipValue = answer.SkipCount ?? 1; + } +
+
+ + @if (maxSkip > 0) + { + +
Maximum: @maxSkip questions
+ } + else + { + +
No more questions to skip
+ } +
+
+ + + @{ + var endSurveyDisplay = answer.ActionType == ConditionActionType.EndSurvey ? "block" : "none"; + } +
+
+ + +
+
+ + +
+ + + Result: + + @GetConditionSummary(answer) +
+
+ + +
+ + + +
+
+ } +
+ } + else + { +
+ + This question has no answers to configure conditions for. +
+ } +
+
+ } +
+ + +
+ + + Back to Questionnaires + + + +
+
+
+
+
+ +@section Scripts { + +} + +@functions { + string GetConditionSummary(AnswerConditionViewModel answer) + { + switch (answer.ActionType) + { + case ConditionActionType.Continue: + return "Continue to the next question normally"; + case ConditionActionType.SkipToQuestion: + if (answer.TargetQuestionNumber.HasValue && answer.TargetQuestionNumber.Value <= Model.Questions.Count) + { + var targetQuestion = Model.Questions[answer.TargetQuestionNumber.Value - 1]; + var questionPreview = targetQuestion.QuestionText.Length > 30 + ? targetQuestion.QuestionText.Substring(0, 30) + "..." + : targetQuestion.QuestionText; + return $"Jump to Q{answer.TargetQuestionNumber}: {questionPreview}"; + } + return $"Jump to Question {answer.TargetQuestionNumber}"; + case ConditionActionType.SkipCount: + return $"Skip {answer.SkipCount} question(s)"; + case ConditionActionType.EndSurvey: + return "End the survey immediately"; + default: + return "No condition set"; + } + } +} \ No newline at end of file diff --git a/Web/Areas/Admin/Views/_ViewImports.cshtml b/Web/Areas/Admin/Views/_ViewImports.cshtml index da91766..8deb846 100644 --- a/Web/Areas/Admin/Views/_ViewImports.cshtml +++ b/Web/Areas/Admin/Views/_ViewImports.cshtml @@ -11,6 +11,7 @@ @using Web.ViewModel.AccountVM @using Web.ViewModel.NewsLetterVM @using Web.ViewModel.DashboardVM + @using Services.EmailSend @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/Web/Migrations/20250807114646_AddConditionalLogicToAnswers.Designer.cs b/Web/Migrations/20250807114646_AddConditionalLogicToAnswers.Designer.cs new file mode 100644 index 0000000..b53b16c --- /dev/null +++ b/Web/Migrations/20250807114646_AddConditionalLogicToAnswers.Designer.cs @@ -0,0 +1,844 @@ +// +using System; +using Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Web.Migrations +{ + [DbContext(typeof(SurveyContext))] + [Migration("20250807114646_AddConditionalLogicToAnswers")] + partial class AddConditionalLogicToAnswers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + 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.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") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + 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") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Model.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CVR") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Country") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("Street") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Addresss"); + }); + + modelBuilder.Entity("Model.Answer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("NextQuestionId") + .HasColumnType("int"); + + b.Property("QuestionId") + .HasColumnType("int"); + + b.Property("SkipToEnd") + .HasColumnType("bit"); + + b.Property("Text") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.ToTable("Answers"); + }); + + modelBuilder.Entity("Model.ApplicationUser", 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("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + 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("Model.Banner", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LinkUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Banners"); + }); + + modelBuilder.Entity("Model.Footer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageUlr") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Sitecopyright") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Footers"); + }); + + modelBuilder.Entity("Model.FooterSocialMedia", b => + { + b.Property("FooterId") + .HasColumnType("int"); + + b.Property("SocialId") + .HasColumnType("int"); + + b.HasKey("FooterId", "SocialId"); + + b.HasIndex("SocialId"); + + b.ToTable("FooterSocialMedias"); + }); + + modelBuilder.Entity("Model.Page", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BannerId") + .HasColumnType("int"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FooterId") + .HasColumnType("int"); + + b.Property("Slug") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BannerId"); + + b.HasIndex("FooterId"); + + b.ToTable("Pages"); + }); + + modelBuilder.Entity("Model.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("QuestionnaireId") + .HasColumnType("int"); + + b.Property("Text") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("QuestionnaireId"); + + b.ToTable("Questions"); + }); + + modelBuilder.Entity("Model.Questionnaire", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Questionnaires"); + }); + + modelBuilder.Entity("Model.Response", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("QuestionnaireId") + .HasColumnType("int"); + + b.Property("SubmissionDate") + .HasColumnType("datetime2"); + + b.Property("UserEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("UserName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("QuestionnaireId"); + + b.ToTable("Responses"); + }); + + modelBuilder.Entity("Model.ResponseAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AnswerId") + .HasColumnType("int"); + + b.Property("NextQuestionId") + .HasColumnType("int"); + + b.Property("ResponseDetailId") + .HasColumnType("int"); + + b.Property("SkipToEnd") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDetailId"); + + b.ToTable("ResponseAnswers"); + }); + + modelBuilder.Entity("Model.ResponseDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("QuestionId") + .HasColumnType("int"); + + b.Property("QuestionType") + .HasColumnType("int"); + + b.Property("ResponseId") + .HasColumnType("int"); + + b.Property("TextResponse") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("ResponseId"); + + b.ToTable("ResponseDetails"); + }); + + modelBuilder.Entity("Model.SentNewsletterEamil", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Body") + .HasColumnType("nvarchar(max)"); + + b.Property("Geo") + .HasColumnType("nvarchar(max)"); + + b.Property("IpAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("IsBlocked") + .HasColumnType("bit"); + + b.Property("IsBounced") + .HasColumnType("bit"); + + b.Property("IsClicked") + .HasColumnType("bit"); + + b.Property("IsDelivered") + .HasColumnType("bit"); + + b.Property("IsOpened") + .HasColumnType("bit"); + + b.Property("IsSent") + .HasColumnType("bit"); + + b.Property("IsSpam") + .HasColumnType("bit"); + + b.Property("IsUnsubscribed") + .HasColumnType("bit"); + + b.Property("ReceivedActivity") + .HasColumnType("datetime2"); + + b.Property("RecipientEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("SentDate") + .HasColumnType("datetime2"); + + b.Property("Subject") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("SentNewsletterEamils"); + }); + + modelBuilder.Entity("Model.SocialMedia", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("SocialMedia"); + }); + + modelBuilder.Entity("Model.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsSubscribed") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + 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("Model.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Model.ApplicationUser", 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("Model.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Model.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Model.Answer", b => + { + b.HasOne("Model.Question", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("Model.FooterSocialMedia", b => + { + b.HasOne("Model.Footer", "Footer") + .WithMany("FooterSocialMedias") + .HasForeignKey("FooterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Model.SocialMedia", "SocialMedia") + .WithMany("FooterSocialMedias") + .HasForeignKey("SocialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Footer"); + + b.Navigation("SocialMedia"); + }); + + modelBuilder.Entity("Model.Page", b => + { + b.HasOne("Model.Banner", "banner") + .WithMany() + .HasForeignKey("BannerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Model.Footer", "footer") + .WithMany() + .HasForeignKey("FooterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("banner"); + + b.Navigation("footer"); + }); + + modelBuilder.Entity("Model.Question", b => + { + b.HasOne("Model.Questionnaire", "Questionnaire") + .WithMany("Questions") + .HasForeignKey("QuestionnaireId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Questionnaire"); + }); + + modelBuilder.Entity("Model.Response", b => + { + b.HasOne("Model.Questionnaire", "Questionnaire") + .WithMany() + .HasForeignKey("QuestionnaireId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Questionnaire"); + }); + + modelBuilder.Entity("Model.ResponseAnswer", b => + { + b.HasOne("Model.ResponseDetail", "ResponseDetail") + .WithMany("ResponseAnswers") + .HasForeignKey("ResponseDetailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDetail"); + }); + + modelBuilder.Entity("Model.ResponseDetail", b => + { + b.HasOne("Model.Question", "Question") + .WithMany() + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Model.Response", "Response") + .WithMany("ResponseDetails") + .HasForeignKey("ResponseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + + b.Navigation("Response"); + }); + + modelBuilder.Entity("Model.Footer", b => + { + b.Navigation("FooterSocialMedias"); + }); + + modelBuilder.Entity("Model.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Model.Questionnaire", b => + { + b.Navigation("Questions"); + }); + + modelBuilder.Entity("Model.Response", b => + { + b.Navigation("ResponseDetails"); + }); + + modelBuilder.Entity("Model.ResponseDetail", b => + { + b.Navigation("ResponseAnswers"); + }); + + modelBuilder.Entity("Model.SocialMedia", b => + { + b.Navigation("FooterSocialMedias"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Web/Migrations/20250807114646_AddConditionalLogicToAnswers.cs b/Web/Migrations/20250807114646_AddConditionalLogicToAnswers.cs new file mode 100644 index 0000000..dd348c0 --- /dev/null +++ b/Web/Migrations/20250807114646_AddConditionalLogicToAnswers.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Web.Migrations +{ + /// + public partial class AddConditionalLogicToAnswers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "NextQuestionId", + table: "ResponseAnswers", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "SkipToEnd", + table: "ResponseAnswers", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "NextQuestionId", + table: "Answers", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "SkipToEnd", + table: "Answers", + type: "bit", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "NextQuestionId", + table: "ResponseAnswers"); + + migrationBuilder.DropColumn( + name: "SkipToEnd", + table: "ResponseAnswers"); + + migrationBuilder.DropColumn( + name: "NextQuestionId", + table: "Answers"); + + migrationBuilder.DropColumn( + name: "SkipToEnd", + table: "Answers"); + } + } +} diff --git a/Web/Migrations/20250808101628_AddConditionJsonToAnswers.Designer.cs b/Web/Migrations/20250808101628_AddConditionJsonToAnswers.Designer.cs new file mode 100644 index 0000000..70aa360 --- /dev/null +++ b/Web/Migrations/20250808101628_AddConditionJsonToAnswers.Designer.cs @@ -0,0 +1,835 @@ +// +using System; +using Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Web.Migrations +{ + [DbContext(typeof(SurveyContext))] + [Migration("20250808101628_AddConditionJsonToAnswers")] + partial class AddConditionJsonToAnswers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + 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.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") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + 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") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Model.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CVR") + .HasColumnType("nvarchar(max)"); + + b.Property("City") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Country") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .HasColumnType("nvarchar(max)"); + + b.Property("Street") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Addresss"); + }); + + modelBuilder.Entity("Model.Answer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ConditionJson") + .HasColumnType("nvarchar(max)"); + + b.Property("QuestionId") + .HasColumnType("int"); + + b.Property("Text") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.ToTable("Answers"); + }); + + modelBuilder.Entity("Model.ApplicationUser", 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("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + 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("Model.Banner", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LinkUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Banners"); + }); + + modelBuilder.Entity("Model.Footer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageUlr") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Sitecopyright") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedBy") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Footers"); + }); + + modelBuilder.Entity("Model.FooterSocialMedia", b => + { + b.Property("FooterId") + .HasColumnType("int"); + + b.Property("SocialId") + .HasColumnType("int"); + + b.HasKey("FooterId", "SocialId"); + + b.HasIndex("SocialId"); + + b.ToTable("FooterSocialMedias"); + }); + + modelBuilder.Entity("Model.Page", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BannerId") + .HasColumnType("int"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FooterId") + .HasColumnType("int"); + + b.Property("Slug") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BannerId"); + + b.HasIndex("FooterId"); + + b.ToTable("Pages"); + }); + + modelBuilder.Entity("Model.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("QuestionnaireId") + .HasColumnType("int"); + + b.Property("Text") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("QuestionnaireId"); + + b.ToTable("Questions"); + }); + + modelBuilder.Entity("Model.Questionnaire", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Questionnaires"); + }); + + modelBuilder.Entity("Model.Response", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("QuestionnaireId") + .HasColumnType("int"); + + b.Property("SubmissionDate") + .HasColumnType("datetime2"); + + b.Property("UserEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("UserName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("QuestionnaireId"); + + b.ToTable("Responses"); + }); + + modelBuilder.Entity("Model.ResponseAnswer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AnswerId") + .HasColumnType("int"); + + b.Property("ResponseDetailId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ResponseDetailId"); + + b.ToTable("ResponseAnswers"); + }); + + modelBuilder.Entity("Model.ResponseDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("QuestionId") + .HasColumnType("int"); + + b.Property("QuestionType") + .HasColumnType("int"); + + b.Property("ResponseId") + .HasColumnType("int"); + + b.Property("TextResponse") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("QuestionId"); + + b.HasIndex("ResponseId"); + + b.ToTable("ResponseDetails"); + }); + + modelBuilder.Entity("Model.SentNewsletterEamil", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Body") + .HasColumnType("nvarchar(max)"); + + b.Property("Geo") + .HasColumnType("nvarchar(max)"); + + b.Property("IpAddress") + .HasColumnType("nvarchar(max)"); + + b.Property("IsBlocked") + .HasColumnType("bit"); + + b.Property("IsBounced") + .HasColumnType("bit"); + + b.Property("IsClicked") + .HasColumnType("bit"); + + b.Property("IsDelivered") + .HasColumnType("bit"); + + b.Property("IsOpened") + .HasColumnType("bit"); + + b.Property("IsSent") + .HasColumnType("bit"); + + b.Property("IsSpam") + .HasColumnType("bit"); + + b.Property("IsUnsubscribed") + .HasColumnType("bit"); + + b.Property("ReceivedActivity") + .HasColumnType("datetime2"); + + b.Property("RecipientEmail") + .HasColumnType("nvarchar(max)"); + + b.Property("SentDate") + .HasColumnType("datetime2"); + + b.Property("Subject") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("SentNewsletterEamils"); + }); + + modelBuilder.Entity("Model.SocialMedia", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("SocialMedia"); + }); + + modelBuilder.Entity("Model.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsSubscribed") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + 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("Model.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Model.ApplicationUser", 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("Model.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Model.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Model.Answer", b => + { + b.HasOne("Model.Question", "Question") + .WithMany("Answers") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + }); + + modelBuilder.Entity("Model.FooterSocialMedia", b => + { + b.HasOne("Model.Footer", "Footer") + .WithMany("FooterSocialMedias") + .HasForeignKey("FooterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Model.SocialMedia", "SocialMedia") + .WithMany("FooterSocialMedias") + .HasForeignKey("SocialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Footer"); + + b.Navigation("SocialMedia"); + }); + + modelBuilder.Entity("Model.Page", b => + { + b.HasOne("Model.Banner", "banner") + .WithMany() + .HasForeignKey("BannerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Model.Footer", "footer") + .WithMany() + .HasForeignKey("FooterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("banner"); + + b.Navigation("footer"); + }); + + modelBuilder.Entity("Model.Question", b => + { + b.HasOne("Model.Questionnaire", "Questionnaire") + .WithMany("Questions") + .HasForeignKey("QuestionnaireId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Questionnaire"); + }); + + modelBuilder.Entity("Model.Response", b => + { + b.HasOne("Model.Questionnaire", "Questionnaire") + .WithMany() + .HasForeignKey("QuestionnaireId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Questionnaire"); + }); + + modelBuilder.Entity("Model.ResponseAnswer", b => + { + b.HasOne("Model.ResponseDetail", "ResponseDetail") + .WithMany("ResponseAnswers") + .HasForeignKey("ResponseDetailId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResponseDetail"); + }); + + modelBuilder.Entity("Model.ResponseDetail", b => + { + b.HasOne("Model.Question", "Question") + .WithMany() + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Model.Response", "Response") + .WithMany("ResponseDetails") + .HasForeignKey("ResponseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Question"); + + b.Navigation("Response"); + }); + + modelBuilder.Entity("Model.Footer", b => + { + b.Navigation("FooterSocialMedias"); + }); + + modelBuilder.Entity("Model.Question", b => + { + b.Navigation("Answers"); + }); + + modelBuilder.Entity("Model.Questionnaire", b => + { + b.Navigation("Questions"); + }); + + modelBuilder.Entity("Model.Response", b => + { + b.Navigation("ResponseDetails"); + }); + + modelBuilder.Entity("Model.ResponseDetail", b => + { + b.Navigation("ResponseAnswers"); + }); + + modelBuilder.Entity("Model.SocialMedia", b => + { + b.Navigation("FooterSocialMedias"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Web/Migrations/20250808101628_AddConditionJsonToAnswers.cs b/Web/Migrations/20250808101628_AddConditionJsonToAnswers.cs new file mode 100644 index 0000000..fc25e22 --- /dev/null +++ b/Web/Migrations/20250808101628_AddConditionJsonToAnswers.cs @@ -0,0 +1,70 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Web.Migrations +{ + /// + public partial class AddConditionJsonToAnswers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "NextQuestionId", + table: "ResponseAnswers"); + + migrationBuilder.DropColumn( + name: "SkipToEnd", + table: "ResponseAnswers"); + + migrationBuilder.DropColumn( + name: "NextQuestionId", + table: "Answers"); + + migrationBuilder.DropColumn( + name: "SkipToEnd", + table: "Answers"); + + migrationBuilder.AddColumn( + name: "ConditionJson", + table: "Answers", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ConditionJson", + table: "Answers"); + + migrationBuilder.AddColumn( + name: "NextQuestionId", + table: "ResponseAnswers", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "SkipToEnd", + table: "ResponseAnswers", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "NextQuestionId", + table: "Answers", + type: "int", + nullable: true); + + migrationBuilder.AddColumn( + name: "SkipToEnd", + table: "Answers", + type: "bit", + nullable: false, + defaultValue: false); + } + } +} diff --git a/Web/Migrations/SurveyContextModelSnapshot.cs b/Web/Migrations/SurveyContextModelSnapshot.cs index 1eb65c1..edc09af 100644 --- a/Web/Migrations/SurveyContextModelSnapshot.cs +++ b/Web/Migrations/SurveyContextModelSnapshot.cs @@ -206,6 +206,9 @@ namespace Web.Migrations SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + b.Property("ConditionJson") + .HasColumnType("nvarchar(max)"); + b.Property("QuestionId") .HasColumnType("int"); diff --git a/Web/ViewModel/QuestionnaireVM/ConditionalLogicViewModel.cs b/Web/ViewModel/QuestionnaireVM/ConditionalLogicViewModel.cs new file mode 100644 index 0000000..e2b75f4 --- /dev/null +++ b/Web/ViewModel/QuestionnaireVM/ConditionalLogicViewModel.cs @@ -0,0 +1,54 @@ +using Model; + +namespace Web.ViewModel.QuestionnaireVM +{ + public class SetLogicViewModel + { + public int QuestionnaireId { get; set; } + public string QuestionnaireName { get; set; } = string.Empty; + public List Questions { get; set; } = new List(); + } + + // ViewModel for each question in the logic setup + public class QuestionLogicViewModel + { + public int QuestionId { get; set; } + public string QuestionText { get; set; } = string.Empty; + public QuestionType QuestionType { get; set; } + public int QuestionNumber { get; set; } + public List Answers { get; set; } = new List(); + } + + // ViewModel for each answer's condition + public class AnswerConditionViewModel + { + public int AnswerId { get; set; } + public string AnswerText { get; set; } = string.Empty; + public ConditionActionType ActionType { get; set; } = ConditionActionType.Continue; + public int? TargetQuestionNumber { get; set; } + public int? SkipCount { get; set; } + public string? EndMessage { get; set; } + } + + // Enum for condition action types + public enum ConditionActionType + { + Continue = 0, + SkipToQuestion = 1, + SkipCount = 2, + EndSurvey = 3 + } + + // ViewModel for saving conditions + public class SaveConditionsViewModel + { + public int QuestionnaireId { get; set; } + public List Conditions { get; set; } = new List(); + } + + public class AnswerConditionSaveViewModel + { + public int AnswerId { get; set; } + public string ConditionJson { get; set; } = string.Empty; + } +}