Add backend condition logic for user questionnaire submission handling

This commit is contained in:
Qaisyousuf 2025-08-11 18:06:13 +02:00
parent 2ce5b50c97
commit f6a03302fd
9 changed files with 1236 additions and 114 deletions

View file

@ -23,5 +23,15 @@ namespace Model
public QuestionType QuestionType { get; set; } public QuestionType QuestionType { get; set; }
public string? TextResponse { get; set; } public string? TextResponse { get; set; }
public List<ResponseAnswer> ResponseAnswers { get; set; } = new List<ResponseAnswer>(); public List<ResponseAnswer> ResponseAnswers { get; set; } = new List<ResponseAnswer>();
public ResponseStatus Status { get; set; } = ResponseStatus.Shown;
public string? SkipReason { get; set; } // Why it was skipped (JSON of condition)
}
public enum ResponseStatus
{
Answered = 1, // Question was answered
Shown = 2, // Question was shown but left blank
Skipped = 3 // Question was skipped due to conditional logic
} }
} }

View file

@ -46,9 +46,10 @@ namespace Web.Areas.Admin.Controllers
{ {
var responses = await _context.Responses var responses = await _context.Responses
.Include(r => r.Questionnaire) .Include(r => r.Questionnaire)
.ThenInclude(q => q.Questions.OrderBy(qu => qu.Id))
.Include(r => r.ResponseDetails) .Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.Question) .ThenInclude(rd => rd.Question)
.ThenInclude(q => q.Answers) // Include the Answers entity .ThenInclude(q => q.Answers)
.Include(r => r.ResponseDetails) .Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.ResponseAnswers) .ThenInclude(rd => rd.ResponseAnswers)
.Where(r => r.UserEmail == userEmail) .Where(r => r.UserEmail == userEmail)

View file

@ -5,8 +5,6 @@
ViewData["Title"] = "User Responses"; ViewData["Title"] = "User Responses";
} }
<style> <style>
.stepper-wrapper { .stepper-wrapper {
display: flex; display: flex;
@ -56,20 +54,52 @@
margin-bottom: 10px; margin-bottom: 10px;
} }
#QuestionnairCard{ #QuestionnairCard {
padding: 2rem;
padding:2rem;
border: 0.5px solid #e7e7e7; border: 0.5px solid #e7e7e7;
border-radius:3px; border-radius: 3px;
-webkit-box-shadow: 0px 0px 14px -7px rgba(0,0,0,0.75); -webkit-box-shadow: 0px 0px 14px -7px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 0px 14px -7px rgba(0,0,0,0.75); -moz-box-shadow: 0px 0px 14px -7px rgba(0,0,0,0.75);
box-shadow: 0px 0px 14px -7px rgba(0,0,0,0.75); box-shadow: 0px 0px 14px -7px rgba(0,0,0,0.75);
} }
.status-answered {
background-color: #d4edda;
border-color: #c3e6cb;
color: #155724;
padding: 8px;
border-radius: 4px;
margin-bottom: 5px;
}
.status-shown {
background-color: #fff3cd;
border-color: #ffeaa7;
color: #856404;
padding: 8px;
border-radius: 4px;
margin-bottom: 5px;
}
.status-skipped {
background-color: #d1ecf1;
border-color: #bee5eb;
color: #0c5460;
padding: 8px;
border-radius: 4px;
margin-bottom: 5px;
}
.status-unknown {
background-color: #f8f9fa;
border-color: #dee2e6;
color: #6c757d;
padding: 8px;
border-radius: 4px;
margin-bottom: 5px;
}
</style> </style>
<div class="container-fluid mt-3"> <div class="container-fluid mt-3">
<p> <p>
<a asp-action="Index" class="btn btn-primary btn-sm">Back to list</a> <a asp-action="Index" class="btn btn-primary btn-sm">Back to list</a>
@ -77,7 +107,7 @@
<div class="card p-5 m-3"> <div class="card p-5 m-3">
<div class="bd-callout bd-callout-primary shadow"> <div class="bd-callout bd-callout-primary shadow">
<div class="row p-4"> <div class="row p-4">
<div class="col-md-3 mt-5"> <div class="col-md-3 mt-5">
<h6 class="text-primary font-weight-bold"><i class="bi bi-person"></i> @Model.UserName (@Model.UserEmail)</h6> <h6 class="text-primary font-weight-bold"><i class="bi bi-person"></i> @Model.UserName (@Model.UserEmail)</h6>
@ -90,21 +120,17 @@
<i class="bi bi-file-excel"></i> Excel Reports <i class="bi bi-file-excel"></i> Excel Reports
</a> </a>
</div> </div>
<div class="col-md-9"> <div class="col-md-9">
<div id="user_survey_summary_chart_div" style=" width: auto; height: 200px;"></div> <div id="user_survey_summary_chart_div" style=" width: auto; height: 200px;"></div>
</div> </div>
</div> </div>
<hr class="border border-info border-1 opacity-50"> <hr class="border border-info border-1 opacity-50">
<div class="col-md-12 "> <div class="col-md-12 ">
<div id="chart_div" style="width: 100%; height: 400px;"></div> <div id="chart_div" style="width: 100%; height: 400px;"></div>
</div> </div>
</div> </div>
<!-- Stepper --> <!-- Stepper -->
<div class="stepper-wrapper"> <div class="stepper-wrapper">
@ -141,64 +167,92 @@
</button> </button>
</div> </div>
<div class="col-md-5"> <div class="col-md-5">
<div id="survey_questions_answers_summary_chart_div_@response.Questionnaire.Id" style="width: 100%; height: 200px;"></div> <div id="survey_questions_answers_summary_chart_div_@response.Questionnaire.Id" style="width: 100%; height: 200px;"></div>
</div> </div>
</div> </div>
<!-- Collapsible content --> <!-- Collapsible content -->
<div class="collapse mt-3" id="collapseResponse-@response.Id"> <div class="collapse mt-3" id="collapseResponse-@response.Id">
<table class="table table-responsive w-100 d-block d-md-table table-hover"> <table class="table table-responsive w-100 d-block d-md-table table-hover">
<thead> <thead>
<tr> <tr>
<th>Question</th> <th>Question</th>
<th>Status</th>
<th>Response</th> <th>Response</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach (var detail in response.ResponseDetails) @foreach (var question in response.Questionnaire.Questions.OrderBy(q => q.Id))
{ {
var responseDetail = response.ResponseDetails.FirstOrDefault(rd => rd.QuestionId == question.Id);
<tr> <tr>
<td>@detail.Question.Text</td>
<td> <td>
@if (detail.QuestionType == QuestionType.Text || detail.QuestionType == QuestionType.Slider || detail.QuestionType == QuestionType.Open_ended) <strong>@question.Text</strong>
<br />
<small class="text-muted">Type: @question.Type</small>
</td>
<td>
@if (responseDetail != null)
{ {
<ul class="list-group"> @switch (responseDetail.Status)
<li class="list-group-item d-flex justify-content-between align-items-center border-1"> {
Question type case ResponseStatus.Answered:
<span class="badge text-bg-primary rounded-pill p-1">@detail.QuestionType</span> <div class="status-answered">
</li> <strong>✅ Answered</strong>
</ul> </div>
<br /> break;
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between align-items-center border-1"> case ResponseStatus.Shown:
Answer <div class="status-shown">
<span class="badge text-bg-primary rounded-pill p-1">@detail.TextResponse</span> <strong>⚠️ Shown but not answered</strong>
</li> <br />
</ul> <small>Question was displayed but left blank</small>
</div>
break;
case ResponseStatus.Skipped:
<div class="status-skipped">
<strong>⏭️ Skipped</strong>
<br />
<small>@responseDetail.SkipReason</small>
</div>
break;
}
} }
else else
{ {
<ul class="list-group"> <div class="status-unknown">
<li class="list-group-item d-flex justify-content-between align-items-center border-1"> <strong>❓ No response data</strong>
Question type <br />
<span class="badge text-bg-primary rounded-pill p-1">@detail.QuestionType</span> <small>Status unknown</small>
</li> </div>
</ul> }
<br /> </td>
<ul class="list-group"> <td>
@foreach (var answer in detail.ResponseAnswers) @if (responseDetail != null && responseDetail.Status == ResponseStatus.Answered)
{ {
<li class="list-group-item d-flex justify-content-between align-items-center"> @if (responseDetail.QuestionType == QuestionType.Text || responseDetail.QuestionType == QuestionType.Slider || responseDetail.QuestionType == QuestionType.Open_ended)
Answer {
<span class="badge text-bg-primary rounded-pill p-1">@detail.Question.Answers.FirstOrDefault(a => a.Id == answer.AnswerId)?.Text</span> <div class="alert alert-light">
</li> <strong>Answer:</strong> @responseDetail.TextResponse
} </div>
</ul> }
else if (responseDetail.ResponseAnswers.Any())
{
<div class="d-flex flex-wrap gap-1">
@foreach (var answer in responseDetail.ResponseAnswers)
{
var answerText = question.Answers.FirstOrDefault(a => a.Id == answer.AnswerId)?.Text;
<span class="badge bg-success">@answerText</span>
}
</div>
}
}
else
{
<span class="text-muted">No response provided</span>
} }
</td> </td>
</tr> </tr>
@ -207,7 +261,6 @@
</table> </table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
} }
@ -215,13 +268,10 @@
</div> </div>
</div> </div>
@section Scripts { @section Scripts {
<!-- Include Bootstrap 5 JS for collapse functionality --> <!-- Include Bootstrap 5 JS for collapse functionality -->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript"> <script type="text/javascript">
google.charts.load('current', { packages: ['corechart', 'line'] }); google.charts.load('current', { packages: ['corechart', 'line'] });
@ -261,10 +311,10 @@
var responsesData = [ var responsesData = [
@foreach (var response in Model.Responses) @foreach (var response in Model.Responses)
{ {
@foreach (var detail in response.ResponseDetails.Where(d => d.QuestionType == QuestionType.Slider)) @foreach (var detail in response.ResponseDetails.Where(d => d.QuestionType == QuestionType.Slider))
{ {
@:['@response.Questionnaire.Title - @response.SubmissionDate.ToString("yyyy-MMMM-dd HH:mm")', getDescriptiveValue(@(detail.TextResponse != null ? int.Parse(detail.TextResponse) : 0))], @:['@response.Questionnaire.Title - @response.SubmissionDate.ToString("yyyy-MMMM-dd HH:mm")', getDescriptiveValue(@(detail.TextResponse != null ? int.Parse(detail.TextResponse) : 0))],
} }
} }
]; ];
@ -313,21 +363,21 @@
data.addColumn('number', 'Total Answers'); data.addColumn('number', 'Total Answers');
var userSurveySummaryData = @Html.Raw(JsonConvert.SerializeObject( var userSurveySummaryData = @Html.Raw(JsonConvert.SerializeObject(
Model.Responses Model.Responses
.GroupBy(response => response.UserEmail) .GroupBy(response => response.UserEmail)
.Select(g => new .Select(g => new
{ {
UserName = g.First().UserName, UserName = g.First().UserName,
TotalSurveys = g.Count(), TotalSurveys = g.Count(),
TotalQuestions = g.SelectMany(r => r.ResponseDetails).Count(), TotalQuestions = g.SelectMany(r => r.ResponseDetails).Count(),
TotalAnswers = g.SelectMany(r => r.ResponseDetails.SelectMany(rd => rd.ResponseAnswers)).Count() TotalAnswers = g.SelectMany(r => r.ResponseDetails.SelectMany(rd => rd.ResponseAnswers)).Count()
}) })
.ToList() .ToList()
)); ));
userSurveySummaryData.forEach(function (row) { userSurveySummaryData.forEach(function (row) {
data.addRow([row.UserName, row.TotalSurveys, row.TotalQuestions, row.TotalAnswers]); data.addRow([row.UserName, row.TotalSurveys, row.TotalQuestions, row.TotalAnswers]);
}); });
var options = { var options = {
title: 'User Survey Summary', title: 'User Survey Summary',
@ -350,22 +400,22 @@
function drawSurveyQuestionsAnswersSummaryCharts() { function drawSurveyQuestionsAnswersSummaryCharts() {
var surveyQuestionsSummaryData = @Html.Raw(JsonConvert.SerializeObject( var surveyQuestionsSummaryData = @Html.Raw(JsonConvert.SerializeObject(
Model.Responses Model.Responses
.GroupBy(response => response.Questionnaire.Title) .GroupBy(response => response.Questionnaire.Title)
.Select(g => new .Select(g => new
{ {
SurveyTitle = g.Key, SurveyTitle = g.Key,
SurveyId = g.First().Questionnaire.Id, SurveyId = g.First().Questionnaire.Id,
TotalQuestions = g.SelectMany(r => r.ResponseDetails).Count(), TotalQuestions = g.SelectMany(r => r.ResponseDetails).Count(),
TotalAnswers = g.SelectMany(r => r.ResponseDetails.SelectMany(rd => rd.ResponseAnswers)).Count() TotalAnswers = g.SelectMany(r => r.ResponseDetails.SelectMany(rd => rd.ResponseAnswers)).Count()
}) })
.ToList() .ToList()
)); ));
surveyQuestionsSummaryData.forEach(function (row) { surveyQuestionsSummaryData.forEach(function (row) {
drawSingleSurveyChart(row.SurveyTitle, row.SurveyId, row.TotalQuestions, row.TotalAnswers); drawSingleSurveyChart(row.SurveyTitle, row.SurveyId, row.TotalQuestions, row.TotalAnswers);
}); });
} }
function drawSingleSurveyChart(surveyTitle, surveyId, totalQuestions, totalAnswers) { function drawSingleSurveyChart(surveyTitle, surveyId, totalQuestions, totalAnswers) {
var data = new google.visualization.DataTable(); var data = new google.visualization.DataTable();
@ -376,7 +426,7 @@
data.addRow(['Answers', totalAnswers]); data.addRow(['Answers', totalAnswers]);
var options = { var options = {
is3D: true, is3D: true,
titleTextStyle: { color: '#17a2b8', fontSize: 16, bold: true }, titleTextStyle: { color: '#17a2b8', fontSize: 16, bold: true },
}; };
@ -418,4 +468,4 @@
}); });
}); });
</script> </script>
} }

View file

@ -1,19 +1,16 @@
using Data; using Data;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
using Model; using Model;
using Newtonsoft.Json; using Newtonsoft.Json;
using Services.EmailSend; using Services.EmailSend;
using Services.Implemnetation; using Services.Implemnetation;
using Services.Interaces; using Services.Interaces;
using System.Globalization; using System.Globalization;
using System.Security.Cryptography; using System.Text.Json;
using System.Text; using System.Text.Json.Serialization;
using Web.ViewModel.AnswerVM;
using Web.ViewModel.QuestionnaireVM; using Web.ViewModel.QuestionnaireVM;
using Web.ViewModel.QuestionVM;
namespace Web.Controllers namespace Web.Controllers
{ {
@ -110,9 +107,14 @@ namespace Web.Controllers
} }
[HttpPost] [HttpPost]
public IActionResult DisplayQuestionnaire([FromForm] ResponseQuestionnaireViewModel questionnaire) public IActionResult DisplayQuestionnaire([FromForm] ResponseQuestionnaireViewModel questionnaire)
{ {
bool hasSubmitted = _context.Responses.Any(r => r.QuestionnaireId == questionnaire.Id && r.UserEmail == questionnaire.Email); bool hasSubmitted = _context.Responses.Any(r => r.QuestionnaireId == questionnaire.Id && r.UserEmail == questionnaire.Email);
// Get the actual questionnaire from database
var dbQuestionnaire = _questionnaireRepository.GetQuestionnaireWithQuestionAndAnswer(questionnaire.Id);
var cetZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); var cetZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
var cetTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, cetZone); var cetTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, cetZone);
var response = new Response var response = new Response
@ -121,17 +123,7 @@ namespace Web.Controllers
UserName = questionnaire.UserName, UserName = questionnaire.UserName,
UserEmail = questionnaire.Email, UserEmail = questionnaire.Email,
SubmissionDate = cetTime, SubmissionDate = cetTime,
ResponseDetails = questionnaire.Questions.Select(q => new ResponseDetail ResponseDetails = CreateResponseDetailsForAllQuestions(dbQuestionnaire, questionnaire.Questions, questionnaire.QuestionsShown, questionnaire.QuestionsSkipped)
{
QuestionId = q.Id,
QuestionType = q.Type,
TextResponse = (q.Type == QuestionType.Open_ended || q.Type == QuestionType.Text || q.Type == QuestionType.Slider)
? string.Join(" ", q.SelectedText)
: null,
ResponseAnswers = q.SelectedAnswerIds
.Select(aid => new ResponseAnswer { AnswerId = aid })
.ToList()
}).ToList()
}; };
_context.Responses.Add(response); _context.Responses.Add(response);
@ -166,6 +158,128 @@ namespace Web.Controllers
return RedirectToAction(nameof(ThankYou)); return RedirectToAction(nameof(ThankYou));
} }
private List<ResponseDetail> CreateResponseDetailsForAllQuestions(Questionnaire questionnaire, List<ResponseQuestionViewModel> answeredQuestions, string questionsShownJson, string questionsSkippedJson)
{
var responseDetails = new List<ResponseDetail>();
// Parse tracking data
List<int> questionsShown = new List<int>();
List<SkippedQuestionInfo> questionsSkipped = new List<SkippedQuestionInfo>();
try
{
if (!string.IsNullOrEmpty(questionsShownJson))
{
questionsShown = System.Text.Json.JsonSerializer.Deserialize<List<int>>(questionsShownJson) ?? new List<int>();
}
if (!string.IsNullOrEmpty(questionsSkippedJson))
{
questionsSkipped = System.Text.Json.JsonSerializer.Deserialize<List<SkippedQuestionInfo>>(questionsSkippedJson) ?? new List<SkippedQuestionInfo>();
}
}
catch (System.Text.Json.JsonException ex)
{
// Log error if needed
Console.WriteLine($"Error parsing tracking data: {ex.Message}");
}
// Get ALL questions from the questionnaire
var allQuestions = questionnaire.Questions.OrderBy(q => q.Id).ToList();
foreach (var dbQuestion in allQuestions)
{
var questionNumber = GetQuestionNumber(dbQuestion.Id, allQuestions);
var answeredQuestion = answeredQuestions.FirstOrDefault(aq => aq.Id == dbQuestion.Id);
var skippedInfo = questionsSkipped.FirstOrDefault(sq => sq.QuestionNumber == questionNumber);
ResponseDetail responseDetail;
if (answeredQuestion != null && HasValidResponse(answeredQuestion))
{
// Question was ANSWERED
responseDetail = new ResponseDetail
{
QuestionId = dbQuestion.Id,
QuestionType = dbQuestion.Type,
Status = ResponseStatus.Answered,
TextResponse = (dbQuestion.Type == QuestionType.Open_ended ||
dbQuestion.Type == QuestionType.Text ||
dbQuestion.Type == QuestionType.Slider)
? string.Join(" ", answeredQuestion.SelectedText)
: null,
ResponseAnswers = answeredQuestion.SelectedAnswerIds
.Select(aid => new ResponseAnswer { AnswerId = aid })
.ToList()
};
}
else if (skippedInfo != null)
{
// Question was SKIPPED due to conditional logic
responseDetail = new ResponseDetail
{
QuestionId = dbQuestion.Id,
QuestionType = dbQuestion.Type,
Status = ResponseStatus.Skipped,
SkipReason = skippedInfo.Reason
};
}
else if (questionsShown.Contains(questionNumber))
{
// Question was SHOWN but left blank
responseDetail = new ResponseDetail
{
QuestionId = dbQuestion.Id,
QuestionType = dbQuestion.Type,
Status = ResponseStatus.Shown,
SkipReason = null
};
}
else
{
// Fallback - assume shown if no tracking data available
responseDetail = new ResponseDetail
{
QuestionId = dbQuestion.Id,
QuestionType = dbQuestion.Type,
Status = ResponseStatus.Shown,
SkipReason = null
};
}
responseDetails.Add(responseDetail);
}
return responseDetails;
}
private int GetQuestionNumber(int questionId, List<Question> allQuestions)
{
return allQuestions.FindIndex(q => q.Id == questionId) + 1;
}
private bool HasValidResponse(ResponseQuestionViewModel question)
{
bool hasTextResponse = question.SelectedText != null &&
question.SelectedText.Any(text => !string.IsNullOrWhiteSpace(text));
bool hasAnswerResponse = question.SelectedAnswerIds != null &&
question.SelectedAnswerIds.Any();
return hasTextResponse || hasAnswerResponse;
}
// Add this class for JSON deserialization
public class SkippedQuestionInfo
{
[JsonPropertyName("questionNumber")]
public int QuestionNumber { get; set; }
[JsonPropertyName("reason")]
public string Reason { get; set; } = string.Empty;
}
// ✅ COMPLETE CORRECTED METHOD: Danish Thank You Email Body // ✅ COMPLETE CORRECTED METHOD: Danish Thank You Email Body
private static string GenerateThankYouEmailBody(string userName) private static string GenerateThankYouEmailBody(string userName)
{ {

View file

@ -0,0 +1,841 @@
// <auto-generated />
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("20250811130002_AddResponseStatus")]
partial class AddResponseStatus
{
/// <inheritdoc />
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<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Model.Address", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("CVR")
.HasColumnType("nvarchar(max)");
b.Property<string>("City")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Country")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Mobile")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PostalCode")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("State")
.HasColumnType("nvarchar(max)");
b.Property<string>("Street")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Addresss");
});
modelBuilder.Entity("Model.Answer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ConditionJson")
.HasColumnType("nvarchar(max)");
b.Property<int>("QuestionId")
.HasColumnType("int");
b.Property<string>("Text")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.ToTable("Answers");
});
modelBuilder.Entity("Model.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FirstName")
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ImageUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("LinkUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Banners");
});
modelBuilder.Entity("Model.Footer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ImageUlr")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("LastUpdated")
.HasColumnType("datetime2");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Owner")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Sitecopyright")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("UpdatedBy")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Footers");
});
modelBuilder.Entity("Model.FooterSocialMedia", b =>
{
b.Property<int>("FooterId")
.HasColumnType("int");
b.Property<int>("SocialId")
.HasColumnType("int");
b.HasKey("FooterId", "SocialId");
b.HasIndex("SocialId");
b.ToTable("FooterSocialMedias");
});
modelBuilder.Entity("Model.Page", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BannerId")
.HasColumnType("int");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("FooterId")
.HasColumnType("int");
b.Property<string>("Slug")
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("BannerId");
b.HasIndex("FooterId");
b.ToTable("Pages");
});
modelBuilder.Entity("Model.Question", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("QuestionnaireId")
.HasColumnType("int");
b.Property<string>("Text")
.HasColumnType("nvarchar(max)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("QuestionnaireId");
b.ToTable("Questions");
});
modelBuilder.Entity("Model.Questionnaire", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Questionnaires");
});
modelBuilder.Entity("Model.Response", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("QuestionnaireId")
.HasColumnType("int");
b.Property<DateTime>("SubmissionDate")
.HasColumnType("datetime2");
b.Property<string>("UserEmail")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserName")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionnaireId");
b.ToTable("Responses");
});
modelBuilder.Entity("Model.ResponseAnswer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("AnswerId")
.HasColumnType("int");
b.Property<int>("ResponseDetailId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ResponseDetailId");
b.ToTable("ResponseAnswers");
});
modelBuilder.Entity("Model.ResponseDetail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("QuestionId")
.HasColumnType("int");
b.Property<int>("QuestionType")
.HasColumnType("int");
b.Property<int>("ResponseId")
.HasColumnType("int");
b.Property<string>("SkipReason")
.HasColumnType("nvarchar(max)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("TextResponse")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.HasIndex("ResponseId");
b.ToTable("ResponseDetails");
});
modelBuilder.Entity("Model.SentNewsletterEamil", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Body")
.HasColumnType("nvarchar(max)");
b.Property<string>("Geo")
.HasColumnType("nvarchar(max)");
b.Property<string>("IpAddress")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsBlocked")
.HasColumnType("bit");
b.Property<bool>("IsBounced")
.HasColumnType("bit");
b.Property<bool>("IsClicked")
.HasColumnType("bit");
b.Property<bool>("IsDelivered")
.HasColumnType("bit");
b.Property<bool>("IsOpened")
.HasColumnType("bit");
b.Property<bool>("IsSent")
.HasColumnType("bit");
b.Property<bool>("IsSpam")
.HasColumnType("bit");
b.Property<bool>("IsUnsubscribed")
.HasColumnType("bit");
b.Property<DateTime>("ReceivedActivity")
.HasColumnType("datetime2");
b.Property<string>("RecipientEmail")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("SentDate")
.HasColumnType("datetime2");
b.Property<string>("Subject")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("SentNewsletterEamils");
});
modelBuilder.Entity("Model.SocialMedia", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("SocialMedia");
});
modelBuilder.Entity("Model.Subscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsSubscribed")
.HasColumnType("bit");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Model.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Model.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

View file

@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Web.Migrations
{
/// <inheritdoc />
public partial class AddResponseStatus : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SkipReason",
table: "ResponseDetails",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "Status",
table: "ResponseDetails",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SkipReason",
table: "ResponseDetails");
migrationBuilder.DropColumn(
name: "Status",
table: "ResponseDetails");
}
}
}

View file

@ -531,6 +531,12 @@ namespace Web.Migrations
b.Property<int>("ResponseId") b.Property<int>("ResponseId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("SkipReason")
.HasColumnType("nvarchar(max)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("TextResponse") b.Property<string>("TextResponse")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");

View file

@ -20,7 +20,8 @@ namespace Web.ViewModel.QuestionnaireVM
public List<ResponseQuestionViewModel> Questions { get; set; } = new List<ResponseQuestionViewModel>(); public List<ResponseQuestionViewModel> Questions { get; set; } = new List<ResponseQuestionViewModel>();
public string QuestionsShown { get; set; } = string.Empty;
public string QuestionsSkipped { get; set; } = string.Empty;
} }
} }

View file

@ -314,6 +314,13 @@
<section class="hero container card"> <section class="hero container card">
<form id="questionnaireForm" method="post" asp-action="DisplayQuestionnaire"> <form id="questionnaireForm" method="post" asp-action="DisplayQuestionnaire">
<input type="hidden" name="Id" value="@Model.Id"> <input type="hidden" name="Id" value="@Model.Id">
<input type="hidden" name="Title" value="@Model.Title">
<input type="hidden" name="Description" value="@Model.Description">
<!-- Add these new hidden inputs for tracking -->
<input type="hidden" id="questionsShown" name="QuestionsShown" value="">
<input type="hidden" id="questionsSkipped" name="QuestionsSkipped" value="">
<input type="hidden" name="Id" value="@Model.Id">
<input type="hidden" name="Title" value="@Model.Title"> <input type="hidden" name="Title" value="@Model.Title">
<input type="hidden" name="Description" value="@Model.Description"> <input type="hidden" name="Description" value="@Model.Description">
@ -680,9 +687,32 @@
return; return;
} }
// Track the actual navigation path // Track which questions were shown and skipped - FIXED VARIABLE NAMES
let questionsShownArray = [1]; // Start with question 1
let questionsSkippedArray = [];
let navigationPath = [0]; // Start with question 1 (index 0) let navigationPath = [0]; // Start with question 1 (index 0)
function trackQuestionShown(questionNumber) {
if (!questionsShownArray.includes(questionNumber)) {
questionsShownArray.push(questionNumber);
console.log('Question shown:', questionNumber, 'Total shown:', questionsShownArray);
}
}
function trackQuestionSkipped(questionNumber, reason) {
const skipInfo = {
questionNumber: questionNumber,
reason: reason
};
questionsSkippedArray.push(skipInfo);
console.log('Question skipped:', skipInfo);
}
function updateTrackingInputs() {
document.getElementById('questionsShown').value = JSON.stringify(questionsShownArray);
document.getElementById('questionsSkipped').value = JSON.stringify(questionsSkippedArray);
}
// Get conditions from the CURRENT step only // Get conditions from the CURRENT step only
function getCurrentStepConditions() { function getCurrentStepConditions() {
const currentStepElement = steps[currentStep]; const currentStepElement = steps[currentStep];
@ -732,8 +762,16 @@
function jumpToQuestion(questionNumber) { function jumpToQuestion(questionNumber) {
const targetStepIndex = questionNumber - 1; // Convert to 0-based index const targetStepIndex = questionNumber - 1; // Convert to 0-based index
if (targetStepIndex >= 0 && targetStepIndex < steps.length) { if (targetStepIndex >= 0 && targetStepIndex < steps.length) {
const currentQuestionNumber = getCurrentStepNumber();
// Track skipped questions between current and target
for (let i = currentQuestionNumber + 1; i < questionNumber; i++) {
trackQuestionSkipped(i, `Skipped due to jump condition from question ${currentQuestionNumber}`);
}
currentStep = targetStepIndex; currentStep = targetStepIndex;
navigationPath.push(currentStep); // Track the path navigationPath.push(currentStep); // Track the path
trackQuestionShown(questionNumber); // Track the target question as shown
showStep(currentStep); showStep(currentStep);
updateStepper(); updateStepper();
console.log(`Jumped to question ${questionNumber}, path:`, navigationPath); console.log(`Jumped to question ${questionNumber}, path:`, navigationPath);
@ -741,10 +779,21 @@
} }
function skipQuestions(skipCount) { function skipQuestions(skipCount) {
const currentQuestionNumber = getCurrentStepNumber();
const newStepIndex = currentStep + skipCount + 1; const newStepIndex = currentStep + skipCount + 1;
// Track skipped questions
for (let i = 1; i <= skipCount; i++) {
const skippedQuestionNumber = currentQuestionNumber + i;
if (skippedQuestionNumber <= steps.length) {
trackQuestionSkipped(skippedQuestionNumber, `Skipped due to skip condition from question ${currentQuestionNumber}`);
}
}
if (newStepIndex < steps.length) { if (newStepIndex < steps.length) {
currentStep = newStepIndex; currentStep = newStepIndex;
navigationPath.push(currentStep); // Track the path navigationPath.push(currentStep); // Track the path
trackQuestionShown(getCurrentStepNumber()); // Track the new question as shown
showStep(currentStep); showStep(currentStep);
updateStepper(); updateStepper();
} else { } else {
@ -758,6 +807,13 @@
} }
function endSurvey(endMessage) { function endSurvey(endMessage) {
const currentQuestionNumber = getCurrentStepNumber();
// Track all remaining questions as skipped
for (let i = currentQuestionNumber + 1; i <= steps.length; i++) {
trackQuestionSkipped(i, `Survey ended early from question ${currentQuestionNumber}`);
}
// Hide all steps and show end message // Hide all steps and show end message
steps.forEach(step => step.style.display = 'none'); steps.forEach(step => step.style.display = 'none');
@ -827,7 +883,10 @@
let currentStep = 0; let currentStep = 0;
form.addEventListener('submit', function (event) { form.addEventListener('submit', function (event) {
console.log('Form submission'); updateTrackingInputs();
console.log('Form submission with tracking data');
console.log('Questions shown:', questionsShownArray);
console.log('Questions skipped:', questionsSkippedArray);
const formData = new FormData(form); const formData = new FormData(form);
formData.forEach((value, key) => { formData.forEach((value, key) => {
console.log(`${key}: ${value}`); console.log(`${key}: ${value}`);
@ -876,6 +935,7 @@
if (currentStep < steps.length - 1) { if (currentStep < steps.length - 1) {
currentStep++; currentStep++;
navigationPath.push(currentStep); // Track normal navigation navigationPath.push(currentStep); // Track normal navigation
trackQuestionShown(getCurrentStepNumber()); // Track question as shown
showStep(currentStep); showStep(currentStep);
updateStepper(); updateStepper();
console.log(`Normal next to question ${currentStep + 1}, path:`, navigationPath); console.log(`Normal next to question ${currentStep + 1}, path:`, navigationPath);
@ -971,4 +1031,4 @@
}); });
} }
</script> </script>
} }