Add backend condition logic for user questionnaire submission handling
This commit is contained in:
parent
2ce5b50c97
commit
f6a03302fd
9 changed files with 1236 additions and 114 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -100,12 +130,8 @@
|
||||||
<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">
|
||||||
@foreach (var response in Model.Responses)
|
@foreach (var response in Model.Responses)
|
||||||
|
|
@ -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,9 +268,6 @@
|
||||||
</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>
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
841
Web/Migrations/20250811130002_AddResponseStatus.Designer.cs
generated
Normal file
841
Web/Migrations/20250811130002_AddResponseStatus.Designer.cs
generated
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Web/Migrations/20250811130002_AddResponseStatus.cs
Normal file
39
Web/Migrations/20250811130002_AddResponseStatus.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue