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 // Prepare request body
var requestBody = new var requestBody = new
{ {
model = "babbage-002", model = "gpt-3.5-turbo",
prompt = inputText, prompt = inputText,
max_tokens = 100 max_tokens = 100
@ -77,8 +77,6 @@ namespace Web.Areas.Admin.Controllers
// Make HTTP POST request to OpenAI API // 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")); 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 // Read response content
string responseBody = await response.Content.ReadAsStringAsync(); 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; var toEmail = viewModel.Email;

View file

@ -13,8 +13,8 @@
<div class="row"> <div class="row">
<form id="generateForm"> <form id="generateForm">
<div class="mb-3 col-12"> <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> <span class="text-danger" id="inputTextError"></span>
</div> </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 Services.Interaces;
using System.Globalization; using System.Globalization;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using Web.ViewModel.AnswerVM;
using Web.ViewModel.QuestionnaireVM; using Web.ViewModel.QuestionnaireVM;
using Web.ViewModel.QuestionVM;
namespace Web.Controllers namespace Web.Controllers
{ {
public class QuestionnaireResponseController : Controller public class QuestionnaireResponseController : Controller
{ {
private readonly IQuestionnaireRepository _questionnaireRepository; private readonly IQuestionnaireRepository _questionnaireRepository;
private readonly SurveyContext _context;
public QuestionnaireResponseController(IQuestionnaireRepository questionnaireRepository) public QuestionnaireResponseController(IQuestionnaireRepository questionnaireRepository,SurveyContext context)
{ {
_questionnaireRepository = questionnaireRepository; _questionnaireRepository = questionnaireRepository;
_context = context;
} }
public IActionResult Index() public IActionResult Index()
{ {
@ -28,49 +36,108 @@ namespace Web.Controllers
return View(); 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); 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"; ViewData["Title"] = "DisplayQuestionnaire";
Layout = "~/Views/Shared/_QuestionnaireResponse.cshtml"; Layout = "~/Views/Shared/_QuestionnaireResponse.cshtml";
@ -124,12 +124,17 @@
</style> </style>
<div class="QuestionContainer"> <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> <h4>@Model.Title</h4>
<p>@Html.Raw(Model.Description) </p> <p>@Html.Raw(Model.Description)</p>
<div class="container"> <div class="container">
<div class="row align-items-center"> <div class="row align-items-center">
@ -141,7 +146,7 @@
var question = Model.Questions[i]; var question = Model.Questions[i];
string stepClass = i == 0 ? "active" : ""; // Adjusted the index to start from the first question string stepClass = i == 0 ? "active" : ""; // Adjusted the index to start from the first question
<div class="step-indicator @(stepClass)" data-step-index="@i"> <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> <span class="step-label">@question.Type</span>
</div> </div>
} }
@ -149,10 +154,20 @@
</div> </div>
<!-- Form Content --> <!-- Form Content -->
<div class="col-md-9"> <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]; 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 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" : "")"> <div class="step @(i == 0 ? "active" : "")">
<p class="font-weight-normal">@(i + 1). @question.Text</p> <p class="font-weight-normal">@(i + 1). @question.Text</p>
<div id="QuestionCard"> <div id="QuestionCard">
@ -160,7 +175,7 @@
{ {
case QuestionType.Text: case QuestionType.Text:
<div class="form-group"> <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> </div>
break; break;
case QuestionType.CheckBox: case QuestionType.CheckBox:
@ -173,32 +188,34 @@
@foreach (var answer in question.Answers) @foreach (var answer in question.Answers)
{ {
<div class="form-check"> <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"> <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 + 1)_answer@(answer.Id)"> <label class="form-check-label" for="question@(i)_answer@(answer.Id)">
@answer.Text @answer.Text
</label> </label>
</div> </div>
} }
</div> </div>
break; break;
case QuestionType.TrueFalse: case QuestionType.TrueFalse:
<div class="form-check"> <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> <label class="form-check-label" for="question@(i + 1)_true">True</label>
</div> </div>
<div class="form-check"> <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> <label class="form-check-label" for="question@(i + 1)_false">False</label>
</div> </div>
break; break;
case QuestionType.Open_ended: 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; break;
case QuestionType.Image: 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; break;
case QuestionType.Slider: 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> <output id="question@(i + 1)_output">50</output>
<script> <script>
document.getElementById('question@(i + 1)').addEventListener('input', function () { document.getElementById('question@(i + 1)').addEventListener('input', function () {
@ -210,12 +227,9 @@
<div class="rating" data-question="@i"> <div class="rating" data-question="@i">
@foreach (var answer in question.Answers) @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> <i class="bi bi-star ml-3" data-value="@answer.Id"></i>
} }
</div> </div>
break; break;
default: default:
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
@ -223,67 +237,7 @@
</div> </div>
break; 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> </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"> <div class="mt-3">
@if (i > 0) @if (i > 0)
{ {
@ -294,20 +248,18 @@
<button type="button" class="btn btn-primary btn-sm next" id="BannerButon">Next <i class="bi bi-arrow-right"></i></button> <button type="button" class="btn btn-primary btn-sm next" id="BannerButon">Next <i class="bi bi-arrow-right"></i></button>
} }
</div> </div>
</div> </div>
} }
<button type="submit" class="btn btn-primary submit btn-sm mt-4" id="BannerButon" style="display: none;">Submit</button> <button type="submit" class="btn btn-primary submit btn-sm mt-4" id="BannerButon">Submit</button>
</div>
</div>
</div>
</form> </form>
</div>
</div>
</div>
</section> </section>
</div>
</div>
@section Scripts { @section Scripts {
@ -317,6 +269,7 @@
<partial name="_ValidationScriptsPartial" /> <partial name="_ValidationScriptsPartial" />
} }
<script> <script>
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById('questionnaireForm'); const form = document.getElementById('questionnaireForm');
const steps = form.querySelectorAll('.step'); const steps = form.querySelectorAll('.step');
@ -324,6 +277,55 @@
const submitButton = form.querySelector('.submit'); const submitButton = form.querySelector('.submit');
let currentStep = 0; let currentStep = 0;
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);
function showStep(index) { function showStep(index) {
steps.forEach((step, i) => { steps.forEach((step, i) => {
if (i === index) { if (i === index) {
@ -389,9 +391,12 @@
}); });
} }
// Add submit event listener to the form
showStep(currentStep); showStep(currentStep);
updateStepper(); updateStepper();
$('.rating i').on('click', function() {
$('.rating i').on('click', function () {
var value = $(this).data('value'); var value = $(this).data('value');
var questionIndex = $(this).closest('.rating').data('question'); var questionIndex = $(this).closest('.rating').data('question');
var selectedStars = $(this).closest('.rating').find('i.selected').length; var selectedStars = $(this).closest('.rating').find('i.selected').length;
@ -410,13 +415,12 @@
}); });
// Prevent the default action for the anchor tags within the rating // Prevent the default action for the anchor tags within the rating
$('.rating a').on('click', function(e) { $('.rating a').on('click', function (e) {
e.preventDefault(); e.preventDefault();
}); });
}); });
</script> </script>
} }

View file

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

View file

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