Response questionnaire for multiple choice completed

This commit is contained in:
Qais Yousuf 2024-04-19 15:11:18 +02:00
parent 31faec4f73
commit ab3daa46f6
10 changed files with 358 additions and 241 deletions

View file

@ -65,7 +65,7 @@ namespace Web.Areas.Admin.Controllers
// Prepare request body
var requestBody = new
{
model = "babbage-002",
model = "gpt-3.5-turbo",
prompt = inputText,
max_tokens = 100
@ -77,8 +77,6 @@ namespace Web.Areas.Admin.Controllers
// Make HTTP POST request to OpenAI API
var response = await httpClient.PostAsync("https://api.openai.com/v1/completions", new StringContent(jsonContent, Encoding.UTF8, "application/json"));
// Check if request was successful
//response.EnsureSuccessStatusCode();
// Read response content
string responseBody = await response.Content.ReadAsStringAsync();

View file

@ -494,9 +494,9 @@ namespace Web.Areas.Admin.Controllers
var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}?token={tokenWithExpiry}";
//var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}?token={tokenWithExpiry}";
//var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}";
var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}";
var toEmail = viewModel.Email;

View file

@ -13,8 +13,8 @@
<div class="row">
<form id="generateForm">
<div class="mb-3 col-12">
<label for="inputText" class="control-label">Input Text</label>
<textarea id="inputText" name="inputText" class="form-control" rows="4"></textarea>
<textarea id="inputText" name="inputText" class="form-control" rows="4" placeholder="Write here"></textarea>
<span class="text-danger" id="inputTextError"></span>
</div>

View file

@ -1,19 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using Data;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Model;
using Newtonsoft.Json;
using Services.Interaces;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using Web.ViewModel.AnswerVM;
using Web.ViewModel.QuestionnaireVM;
using Web.ViewModel.QuestionVM;
namespace Web.Controllers
{
public class QuestionnaireResponseController : Controller
{
private readonly IQuestionnaireRepository _questionnaireRepository;
private readonly SurveyContext _context;
public QuestionnaireResponseController(IQuestionnaireRepository questionnaireRepository)
public QuestionnaireResponseController(IQuestionnaireRepository questionnaireRepository,SurveyContext context)
{
_questionnaireRepository = questionnaireRepository;
_context = context;
}
public IActionResult Index()
{
@ -28,49 +36,108 @@ namespace Web.Controllers
return View();
}
public IActionResult DisplayQuestionnaire(int id, string token)
public IActionResult DisplayQuestionnaire(int id)
{
// Check if the token is null or empty
if (string.IsNullOrEmpty(token))
{
ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
return View("Error");
}
// Split the token to extract the expiration date and time
string[] tokenParts = token.Split('|');
if (tokenParts.Length != 2)
{
ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
return View("Error");
}
string expiryDateTimeString = tokenParts[1];
// Parse the expiration datetime in UTC format
if (!DateTime.TryParseExact(expiryDateTimeString, "yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime expiryDateTimeUtc))
{
ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
return View("Error");
}
// Convert the expiration datetime to local time
DateTime expiryDateTimeLocal = expiryDateTimeUtc.ToLocalTime();
// Check if the token is expired (accounting for UTC+2 offset)
if (expiryDateTimeLocal < DateTime.Now.AddHours(2))
{
return RedirectToAction(nameof(Error));
}
// Retrieve the questionnaire using the numeric ID
var questionnaire = _questionnaireRepository.GetQuestionnaireWithQuestionAndAnswer(id);
return View(questionnaire);
return View(MapToViewModel(questionnaire));
}
[HttpPost]
public IActionResult DisplayQuestionnaire([FromForm] ResponseQuestionnaireViewModel questionnaire)
{
foreach (var question in questionnaire.Questions)
{
var dbQuestion = _context.Questions.Include(q => q.Answers).FirstOrDefault(q => q.Id == question.Id);
if (dbQuestion != null)
{
foreach (var selectedId in question.SelectedAnswerIds)
{
var selectedAnswer = dbQuestion.Answers.FirstOrDefault(a => a.Id == selectedId);
if (selectedAnswer != null)
{
Console.WriteLine($"Selected Answer: {selectedAnswer.Text}");
// Here you could further process each selected answer, e.g., saving user responses
}
}
}
}
return Ok();
}
private ResponseQuestionnaireViewModel MapToViewModel(Questionnaire questionnaire)
{
var viewModel = new ResponseQuestionnaireViewModel
{
Id = questionnaire.Id,
Title = questionnaire.Title,
Description = questionnaire.Description,
Questions = questionnaire.Questions.Select(q => new ResponseQuestionViewModel
{
Id = q.Id,
Text = q.Text,
Type = q.Type,
Answers = q.Answers.Select(a => new ResponseAnswerViewModel
{
Id = a.Id,
Text = a.Text
}).ToList()
}).ToList()
};
return viewModel;
}
}
}
//// Check if the token is null or empty
//if (string.IsNullOrEmpty(t))
//{
// ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
// return View("Error");
//}
//// Split the token to extract the expiration date and time
//string[] tokenParts = t.Split('|');
//if (tokenParts.Length != 2)
//{
// ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
// return View("Error");
//}
//string expiryDateTimeString = tokenParts[1];
//// Parse the expiration datetime in UTC format
//if (!DateTime.TryParseExact(expiryDateTimeString, "yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime expiryDateTimeUtc))
//{
// ViewBag.ErrorMessage = "The URL is invalid. Please provide a valid token.";
// return View("Error");
//}
//// Convert the expiration datetime to local time
//DateTime expiryDateTimeLocal = expiryDateTimeUtc.ToLocalTime();
//// Check if the token is expired (accounting for UTC+2 offset)
//if (expiryDateTimeLocal < DateTime.Now.AddHours(2))
//{
// return RedirectToAction(nameof(Error));
//}
// Retrieve the questionnaire using the numeric ID

View file

@ -0,0 +1,8 @@
namespace Web.ViewModel.QuestionnaireVM
{
public class ResponseAnswerViewModel
{
public int Id { get; set; } // Answer ID
public string? Text { get; set; } // Answer text
}
}

View file

@ -0,0 +1,18 @@
using Model;
using Web.ViewModel.AnswerVM;
namespace Web.ViewModel.QuestionnaireVM
{
public class ResponseQuestionViewModel
{
public int Id { get; set; } // Question ID
public string? Text { get; set; } // Question text
public QuestionType Type { get; set; } // Question type
// List of selectable answers
public List<ResponseAnswerViewModel> Answers { get; set; } = new List<ResponseAnswerViewModel>();
// IDs of selected answers, used for submitting form data
public List<int> SelectedAnswerIds { get; set; } = new List<int>();
}
}

View file

@ -0,0 +1,17 @@
using Web.ViewModel.QuestionVM;
namespace Web.ViewModel.QuestionnaireVM
{
public class ResponseQuestionnaireViewModel
{
public int Id { get; set; } // Questionnaire ID
public string? Title { get; set; } // Title of the questionnaire
public string? Description { get; set; } // Description of the questionnaire
// Collection of questions
public List<ResponseQuestionViewModel> Questions { get; set; } = new List<ResponseQuestionViewModel>();
}
}

View file

@ -1,4 +1,4 @@
@model Questionnaire
@model ResponseQuestionnaireViewModel
@{
ViewData["Title"] = "DisplayQuestionnaire";
Layout = "~/Views/Shared/_QuestionnaireResponse.cshtml";
@ -124,12 +124,17 @@
</style>
<div class="QuestionContainer">
<section class="hero container card">
<section class="hero container card">
<form id="questionnaireForm" method="post" asp-action="DisplayQuestionnaire">
<input type="hidden" name="Id" value="@Model.Id">
<input type="hidden" name="Title" value="@Model.Title">
<input type="hidden" name="Description" value="@Model.Description">
<h4>@Model.Title</h4>
<p>@Html.Raw(Model.Description)</p>
<h4>@Model.Title</h4>
<p>@Html.Raw(Model.Description) </p>
<div class="container">
<div class="row align-items-center">
@ -141,7 +146,7 @@
var question = Model.Questions[i];
string stepClass = i == 0 ? "active" : ""; // Adjusted the index to start from the first question
<div class="step-indicator @(stepClass)" data-step-index="@i">
<span class="step-number">@((i + 1)). </span> <!-- Adjusted to start from 1 -->
<span class="step-number">@((i + 1)). </span>
<span class="step-label">@question.Type</span>
</div>
}
@ -149,18 +154,28 @@
</div>
<!-- Form Content -->
<div class="col-md-9">
<form id="questionnaireForm">
@for (int i = 0; i < Model.Questions.Count; i++)
@for (int i = 0; i < Model.Questions.Count; i++)
{
var question = Model.Questions[i];
<input type="hidden" name="Id" value="@question.Id">
<input type="hidden" name="Questions[@i].Text" value="@question.Text">
<input type="hidden" name="Questions[@i].Id" value="@question.Id">
<input type="hidden" name="Questions[@i].Type" value="@((int)question.Type)">
@for (int j = 0; j < question.Answers.Count; j++)
{
var question = Model.Questions[i];
<div class="step @(i == 0 ? "active" : "")">
<p class="font-weight-normal">@(i + 1). @question.Text</p>
<div id="QuestionCard">
var answer = question.Answers[j];
<input type="hidden" name="Questions[@i].Answers[@j].Id" value="@answer.Id">
<input type="hidden" name="Questions[@i].Answers[@j].Text" value="@answer.Text">
<!-- Add more hidden fields as needed for other properties of the answer -->
}
<div class="step @(i == 0 ? "active" : "")">
<p class="font-weight-normal">@(i + 1). @question.Text</p>
<div id="QuestionCard">
@switch (question.Type)
{
case QuestionType.Text:
<div class="form-group">
<input type="text" class="form-control" name="question@(i + 1)" placeholder="Enter answer">
<input type="text" class="form-control" name="Questions[@i].SelectedAnswerIds" placeholder="Enter answer">
</div>
break;
case QuestionType.CheckBox:
@ -173,32 +188,34 @@
@foreach (var answer in question.Answers)
{
<div class="form-check">
<input class="form-check-input" type="checkbox" id="question@(i + 1)_answer@(answer.Id)" name="question@(i + 1)" value="@answer.Id">
<label class="form-check-label" for="question@(i + 1)_answer@(answer.Id)">
<input class="form-check-input answer-input" type="checkbox" id="question@(i)_answer@(answer.Id)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id">
<label class="form-check-label" for="question@(i)_answer@(answer.Id)">
@answer.Text
</label>
</div>
}
</div>
break;
case QuestionType.TrueFalse:
<div class="form-check">
<input class="form-check-input" type="radio" id="question@(i + 1)_true" name="question@(i + 1)" value="true">
<input class="form-check-input" type="radio" id="question@(i + 1)_true" name="Questions[@i].SelectedAnswerIds" value="true">
<label class="form-check-label" for="question@(i + 1)_true">True</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="question@(i + 1)_false" name="question@(i + 1)" value="false">
<input class="form-check-input" type="radio" id="question@(i + 1)_false" name="Questions[@i].SelectedAnswerIds" value="false">
<label class="form-check-label" for="question@(i + 1)_false">False</label>
</div>
break;
case QuestionType.Open_ended:
<textarea class="form-control" id="question@(i + 1)" name="question@(i + 1)" rows="3" placeholder="Enter answer"></textarea>
<textarea class="form-control" id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds" rows="3" placeholder="Enter answer"></textarea>
break;
case QuestionType.Image:
<input type="file" class="form-control-file" id="question@(i + 1)" name="question@(i + 1)">
<input type="file" class="form-control-file" id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds">
break;
case QuestionType.Slider:
<input type="range" class="form-range" id="question@(i + 1)" name="question@(i + 1)" min="0" max="100" step="1">
<input type="range" class="form-range" id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds" min="0" max="100" step="1">
<output id="question@(i + 1)_output">50</output>
<script>
document.getElementById('question@(i + 1)').addEventListener('input', function () {
@ -207,15 +224,12 @@
</script>
break;
case QuestionType.Rating:
<div class="rating" data-question="@i">
@foreach (var answer in question.Answers)
{
@* <span class="text-white">@answer.Id</span> *@
<i class="bi bi-star ml-3" data-value="@answer.Id"></i>
}
</div>
<div class="rating" data-question="@i">
@foreach (var answer in question.Answers)
{
<i class="bi bi-star ml-3" data-value="@answer.Id"></i>
}
</div>
break;
default:
<div class="alert alert-danger" role="alert">
@ -223,91 +237,29 @@
</div>
break;
}
@* @switch (question.Type)
{
case QuestionType.Text:
<div class="form-group">
<input type="text" class="form-control" name="question@(i + 1)" placeholder="Enter answer">
</div>
break;
case QuestionType.CheckBox:
case QuestionType.Multiple_choice:
case QuestionType.Rating:
case QuestionType.Likert:
case QuestionType.Matrix:
case QuestionType.Demographic:
case QuestionType.Ranking:
<div class="form-group">
@foreach (var answer in question.Answers)
{
<div class="form-check">
<input class="form-check-input" type="checkbox" id="question@(i + 1)_answer@(answer.Id)" name="question@(i + 1)" value="@answer.Id">
<label class="form-check-label" for="question@(i + 1)_answer@(answer.Id)">
@answer.Text
</label>
</div>
}
</div>
break;
case QuestionType.TrueFalse:
<div class="form-check">
<input class="form-check-input" type="radio" id="question@(i + 1)_true" name="question@(i + 1)" value="true">
<label class="form-check-label" for="question@(i + 1)_true">True</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" id="question@(i + 1)_false" name="question@(i + 1)" value="false">
<label class="form-check-label" for="question@(i + 1)_false">False</label>
</div>
break;
case QuestionType.Open_ended:
<textarea class="form-control" id="question@(i + 1)" name="question@(i + 1)" rows="3" placeholder="Enter answer"></textarea>
break;
case QuestionType.Image:
<input type="file" class="form-control-file" id="question@(i + 1)" name="question@(i + 1)">
break;
case QuestionType.Slider:
<input type="range" class="form-range" id="question@(i + 1)" name="question@(i + 1)" min="0" max="100" step="1">
<output id="question@(i + 1)_output">50</output>
<script>
document.getElementById('question@(i + 1)').addEventListener('input', function () {
document.getElementById('question@(i + 1)_output').value = this.value;
});
</script>
break;
default:
<div class="alert alert-danger" role="alert">
Unsupported question type.
</div>
break;
} *@
</div>
<div class="mt-3">
@if (i > 0)
{
<button type="button" class="btn btn-secondary btn-sm mr-3 prev" id="BannerButon"><i class="bi bi-arrow-left"></i> Previous </button>
}
@if (i < Model.Questions.Count - 1)
{
<button type="button" class="btn btn-primary btn-sm next" id="BannerButon">Next <i class="bi bi-arrow-right"></i></button>
}
</div>
</div>
}
<div class="mt-3">
@if (i > 0)
{
<button type="button" class="btn btn-secondary btn-sm mr-3 prev" id="BannerButon"><i class="bi bi-arrow-left"></i> Previous </button>
}
@if (i < Model.Questions.Count - 1)
{
<button type="button" class="btn btn-primary btn-sm next" id="BannerButon">Next <i class="bi bi-arrow-right"></i></button>
}
</div>
</div>
}
<button type="submit" class="btn btn-primary submit btn-sm mt-4" id="BannerButon">Submit</button>
<button type="submit" class="btn btn-primary submit btn-sm mt-4" id="BannerButon" style="display: none;">Submit</button>
</form>
</div>
</div>
</div>
</section>
</div>
</form>
</section>
</div>
@section Scripts {
@ -317,106 +269,158 @@
<partial name="_ValidationScriptsPartial" />
}
<script>
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById('questionnaireForm');
const steps = form.querySelectorAll('.step');
const stepIndicators = document.querySelectorAll('.step-indicator');
const submitButton = form.querySelector('.submit');
let currentStep = 0;
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById('questionnaireForm');
const steps = form.querySelectorAll('.step');
const stepIndicators = document.querySelectorAll('.step-indicator');
const submitButton = form.querySelector('.submit');
let currentStep = 0;
function showStep(index) {
steps.forEach((step, i) => {
if (i === index) {
step.style.display = 'block';
} else {
step.style.display = 'none';
}
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('questionnaireForm'); // Make sure this is your form ID
form.addEventListener('submit', function (event) {
event.preventDefault(); // Prevent the default form submission action
const formData = new FormData(form);
const refinedFormData = new FormData(); // This will hold only the necessary data
// Iterate over the original FormData entries
formData.forEach((value, key) => {
if (key.includes("SelectedAnswerIds")) {
// Handle checkbox fields specially to ensure only checked boxes are included
let selector = `input[name="${key}"]:checked`;
document.querySelectorAll(selector).forEach(input => {
refinedFormData.append(key, input.value);
});
} else {
// Append other data normally
refinedFormData.append(key, value);
}
});
// Log for debugging to see what will be submitted
console.log('Prepared formData for submission:');
refinedFormData.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// Use Fetch API to submit the refined FormData
fetch(form.action, {
method: 'POST',
body: refinedFormData
}).then(response => response.json()).then(data => {
console.log('Submission successful', data);
// Optional: Redirect or handle post-submission here
}).catch(error => {
console.error('Submission failed', error);
});
});
});
document.getElementById('questionnaireForm').addEventListener('submit', handleSubmit);
stepIndicators.forEach((indicator, i) => {
if (i === index) {
indicator.classList.add('active');
} else {
indicator.classList.remove('active');
}
});
if (index === steps.length - 1) {
submitButton.style.display = 'block';
function showStep(index) {
steps.forEach((step, i) => {
if (i === index) {
step.style.display = 'block';
} else {
submitButton.style.display = 'none';
step.style.display = 'none';
}
}
});
function goToNextStep() {
if (currentStep < steps.length - 1) {
currentStep++;
showStep(currentStep);
stepIndicators.forEach((indicator, i) => {
if (i === index) {
indicator.classList.add('active');
} else {
indicator.classList.remove('active');
}
}
});
function goToPrevStep() {
if (currentStep > 0) {
currentStep--;
showStep(currentStep);
if (index === steps.length - 1) {
submitButton.style.display = 'block';
} else {
submitButton.style.display = 'none';
}
}
function goToNextStep() {
if (currentStep < steps.length - 1) {
currentStep++;
showStep(currentStep);
}
}
function goToPrevStep() {
if (currentStep > 0) {
currentStep--;
showStep(currentStep);
}
}
const nextButtons = form.querySelectorAll('.next');
nextButtons.forEach(button => {
button.addEventListener('click', () => {
goToNextStep();
updateStepper();
});
});
const prevButtons = form.querySelectorAll('.prev');
prevButtons.forEach(button => {
button.addEventListener('click', () => {
goToPrevStep();
updateStepper();
});
});
function updateStepper() {
const currentStepIndex = currentStep;
stepIndicators.forEach((indicator, i) => {
if (i === currentStepIndex) {
indicator.style.backgroundColor = '#33b3ae'; // Change to your primary color
} else {
indicator.style.backgroundColor = ''; // Reset to default color for non-active steps
}
}
const nextButtons = form.querySelectorAll('.next');
nextButtons.forEach(button => {
button.addEventListener('click', () => {
goToNextStep();
updateStepper();
});
});
}
const prevButtons = form.querySelectorAll('.prev');
prevButtons.forEach(button => {
button.addEventListener('click', () => {
goToPrevStep();
updateStepper();
});
});
// Add submit event listener to the form
showStep(currentStep);
updateStepper();
function updateStepper() {
const currentStepIndex = currentStep;
stepIndicators.forEach((indicator, i) => {
if (i === currentStepIndex) {
indicator.style.backgroundColor = '#33b3ae'; // Change to your primary color
} else {
indicator.style.backgroundColor = ''; // Reset to default color for non-active steps
}
});
}
$('.rating i').on('click', function () {
var value = $(this).data('value');
var questionIndex = $(this).closest('.rating').data('question');
var selectedStars = $(this).closest('.rating').find('i.selected').length;
showStep(currentStep);
updateStepper();
$('.rating i').on('click', function() {
var value = $(this).data('value');
var questionIndex = $(this).closest('.rating').data('question');
var selectedStars = $(this).closest('.rating').find('i.selected').length;
// Unselect all stars
$(this).closest('.rating').find('i').removeClass('selected');
// Select the clicked star and all preceding stars
$(this).addClass('selected').prevAll().addClass('selected');
// Update the hidden input value if needed
$('input[name="question' + questionIndex + '_rating"]').val(value);
// Update the label with the number of selected stars
$(this).closest('.rating').next('.selected-count').text(selectedStars);
});
// Prevent the default action for the anchor tags within the rating
$('.rating a').on('click', function(e) {
e.preventDefault();
});
// Unselect all stars
$(this).closest('.rating').find('i').removeClass('selected');
// Select the clicked star and all preceding stars
$(this).addClass('selected').prevAll().addClass('selected');
// Update the hidden input value if needed
$('input[name="question' + questionIndex + '_rating"]').val(value);
// Update the label with the number of selected stars
$(this).closest('.rating').next('.selected-count').text(selectedStars);
});
// Prevent the default action for the anchor tags within the rating
$('.rating a').on('click', function (e) {
e.preventDefault();
});
});
</script>
</script>
}

View file

@ -1,5 +1,6 @@
@using Web
@using Model
@using Web.Models
@using Web.ViewModel.QuestionnaireVM
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *,Web

View file

@ -21,6 +21,10 @@
"MailJet": {
"ApiKey": "f545eee3a4743464b9d25fb9c5ab3f6c",
"SecretKey": "9fa430ef00873fdefe333fdc40ee3f8f"
},
"OpenAI": {
"ApiKey": "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"
}
}