User response backend completed

This commit is contained in:
Qais Yousuf 2024-04-26 11:40:27 +02:00
parent 98ec7a6561
commit 181237e9ff
25 changed files with 1393 additions and 968 deletions

View file

@ -34,7 +34,9 @@ namespace Data
public DbSet<Subscription> Subscriptions { get; set; }
public DbSet<Response> Responses { get; set; }
public DbSet<ResponseDetail> ResponseDetails { get; set; }
public DbSet<ResponseAnswer> ResponseAnswers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
@ -72,16 +74,18 @@ namespace Data
.HasKey(a => a.Id);
//modelBuilder.Entity<Page>()
// .HasOne(p => p.footer)
// .WithMany()
// .HasForeignKey(p => p.FooterId)
// .IsRequired(false)
// .OnDelete(DeleteBehavior.Cascade);
// Questionnaire to Response relationship
modelBuilder.Entity<ResponseDetail>()
.HasOne(rd => rd.Response)
.WithMany(r => r.ResponseDetails)
.HasForeignKey(rd => rd.ResponseId)
.OnDelete(DeleteBehavior.Cascade); // This is safe if only Responses are being deleted leading to ResponseDetails
modelBuilder.Entity<ResponseDetail>()
.HasOne(rd => rd.Question)
.WithMany()
.HasForeignKey(rd => rd.QuestionId)
.OnDelete(DeleteBehavior.Restrict);
base.OnModelCreating(modelBuilder);

22
Model/Response.cs Normal file
View file

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
public class Response
{
public int Id { get; set; }
public int QuestionnaireId { get; set; } // Foreign Key to Questionnaire
[ForeignKey("QuestionnaireId")]
public Questionnaire? Questionnaire { get; set; }
public string? UserName { get; set; } // To store the user's name
public string? UserEmail { get; set; } // To store the user's email
public DateTime SubmissionDate { get; set; }
public List<ResponseDetail> ResponseDetails { get; set; } = new List<ResponseDetail>();
}
}

19
Model/ResponseAnswer.cs Normal file
View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
public class ResponseAnswer
{
public int Id { get; set; }
public int ResponseDetailId { get; set; }
[ForeignKey("ResponseDetailId")]
public ResponseDetail? ResponseDetail { get; set; }
public int AnswerId { get; set; }
}
}

27
Model/ResponseDetail.cs Normal file
View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Model
{
public class ResponseDetail
{
public int Id { get; set; }
public int ResponseId { get; set; }
[ForeignKey("ResponseId")]
public Response? Response { get; set; }
public int QuestionId { get; set; }
[ForeignKey("QuestionId")]
public Question? Question { get; set; }
public QuestionType QuestionType { get; set; }
public string? TextResponse { get; set; }
public List<ResponseAnswer> ResponseAnswers { get; set; } = new List<ResponseAnswer>();
}
}

View file

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Elfie.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Model;
using Services.EmailSend;
@ -465,12 +466,11 @@ namespace Web.Areas.Admin.Controllers
{
// Build the email body with questionnaire details
var questionnairePath = _configuration["Email:Questionnaire"];
int surveyId = viewModel.QuestionnaireId;
//DateTime currentDateTime = DateTime.Now;
//DateTime currentDateTime = viewModel.ExpirationDateTime;
DateTime currentDateTime;
if (viewModel.ExpirationDateTime.HasValue)
{
@ -478,8 +478,7 @@ namespace Web.Areas.Admin.Controllers
}
else
{
// Handle the case when ExpirationDateTime is null
// For example, you can assign the current date and time
currentDateTime = DateTime.Now;
}
@ -494,9 +493,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}?t={tokenWithExpiry}";
var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}";
//var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}";
var toEmail = viewModel.Email;
@ -583,6 +582,32 @@ namespace Web.Areas.Admin.Controllers
// If model state is not valid, return the view with validation errors
return View(viewModel);
}
[HttpGet]
public async Task<IActionResult> ViewResponse(int id) // Pass the response ID
{
var response = await _context.Responses
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.ResponseAnswers)
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.Question) // Include questions for detailed display
.ThenInclude(q => q.Answers) // Include all possible answers for each question
.FirstOrDefaultAsync(r => r.Id == id); // Find the response by ID
if (response == null)
{
return NotFound(); // If no response is found, return a NotFound result
}
return View(response); // Pass the response to the view
}
public string GenerateExpiryToken(DateTime expiryDate)
{
// Generate a unique token, for example, using a cryptographic library or a GUID
@ -593,7 +618,7 @@ namespace Web.Areas.Admin.Controllers
return tokenWithExpiry;
}
}

View file

@ -0,0 +1,50 @@
using Data;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Model;
namespace Web.Areas.Admin.Controllers
{
public class UserResponseController : Controller
{
private readonly SurveyContext _context;
public UserResponseController(SurveyContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
var responses = await GetAllResponsesWithDetailsAsync(); // Fetch the data
return View(responses); // Pass the data to the view
}
private async Task<List<Response>> GetAllResponsesWithDetailsAsync()
{
return await _context.Responses
.Include(r => r.Questionnaire) // Ensure the Questionnaire data is included
.OrderBy(r => r.Id) // Optional: Order by submission date
.ToListAsync();
}
[HttpGet]
public async Task<IActionResult> ViewResponse(int id) // Pass the response ID
{
var response = await _context.Responses
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.Question)
.ThenInclude(q => q.Answers) // Load all possible answers for the questions
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.ResponseAnswers) // Load the answers selected by the user
.AsNoTracking()
.FirstOrDefaultAsync(r => r.Id == id);
if (response == null)
{
return NotFound(); // If no response is found, return a NotFound result
}
return View(response); // Pass the response to the view
}
}
}

View file

@ -86,10 +86,11 @@
</td>
<td class="d-flex justify-content-end">
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-danger btn-s"><i class="bi bi-trash"></i> Delete</a> |
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-primary"><i class="bi bi-pencil-square"></i> Edit</a>|
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-info btn-s"><i class="bi bi-pencil-square"></i> Details</a> |
<a asp-action="SendQuestionnaire" asp-route-id="@item.Id" class="btn btn-success btn-s"><i class="bi bi-pencil-square"></i> Send</a>
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-danger btn-sm"><i class="bi bi-trash"></i> Delete</a> |
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-primary btn-sm"><i class="bi bi-pencil-square"></i> Edit</a>|
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-info btn-sm"><i class="bi bi-pencil-square"></i> Details</a> |
<a asp-action="SendQuestionnaire" asp-route-id="@item.Id" class="btn btn-success btn-sm"><i class="bi bi-pencil-square"></i> Send</a>
</td>
</tr>
}

View file

@ -0,0 +1,41 @@

@model Response
@{
ViewData["Title"] = "ViewResponses";
}
<style>
.selected {
font-weight: bold;
}
</style>
<div class="container">
<p>Response by: @Model.UserName on @Model.SubmissionDate.ToString("dd/MM/yyyy")</p>
@foreach (var detail in Model.ResponseDetails)
{
<h5>Question: @detail.Question.Text</h5>
<h6>All Available Answers:</h6>
<ul>
@foreach (var answer in detail.Question.Answers)
{
<li class="badge badge-secondary">@answer.Text</li>
}
</ul>
<h6>Provided Answers:</h6>
<ul>
@foreach (var answer in detail.ResponseAnswers)
{
<li class="badge badge-success">@detail.Question.Answers.FirstOrDefault(a => a.Id == answer.AnswerId)?.Text</li>
}
</ul>
}
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>

View file

@ -39,6 +39,10 @@
<a asp-controller="Questionnaire" asp-action="index"><span class="bi bi-question-circle"></span> Survey</a>
</li>
<li>
<a asp-controller="UserResponse" asp-action="index"><span class="bi bi-clipboard-data"></span> Response</a>
</li>
<li>
<a asp-controller="newsletters" asp-action="index"><span class="bi bi-newspaper"></span> Subscibers</a>
</li>
@ -84,7 +88,7 @@
</nav>
<main class=" mt-5">
<main>
@RenderBody()
</main>

View file

@ -0,0 +1,56 @@
@model IEnumerable<Model.Response>
@{
ViewData["Title"] = "Response";
}
<div class="container mt-5">
<partial name="_Notification" />
<div class="card bg-default mb-3 ">
<div class="card-header">User Reponse</div>
<div class="card-body">
<h4 class="card-title">User response list</h4>
<table class="table table-responsive w-100 d-block d-md-table">
<thead class="w-auto">
<tr>
<th scope="col">Id</th>
<th scope="col">Questionnaire</th>
<th scope="col">UserName</th>
<th scope="col">UserEmail</th>
<th scope="col">Submission Date</th>
<th scope="col" class="d-flex justify-content-end">Action</th>
</tr>
</thead>
<tbody class="w-auto">
@foreach (var item in Model)
{
<tr class="table-secondary">
<td>@item.Id</td>
<td> <span class="badge bg-primary">@item.Questionnaire.Title</span></td>
<td>@item.UserName</td>
<td>@item.UserName</td>
<td>@item.SubmissionDate</td>
<td class="d-flex justify-content-end">
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-danger btn-sm"><i class="bi bi-trash"></i> Delete</a> |
<a asp-action="ViewResponse" asp-route-id="@item.Id" class="btn btn-warning btn-sm"><i class="bi bi-pencil-square"></i> Details</a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>

View file

@ -0,0 +1,462 @@
@model Response
@{
ViewData["Title"] = "ViewResponses";
}
<style>
<style>
.font-weight-bold {
font-weight: bold;
}
.text-primary {
color: #121212; /* Bootstrap primary color */
}
.selected-answer {
color: #28a745; /* Bootstrap success color */
}
.star-rating .text-primary {
color:goldenrod; /* Bootstrap primary blue */
font-weight: bold; /* Make text bold */
}
.star-rating .text-secondary {
color: #6c757d; /* Bootstrap secondary text color */
}
.star-rating .text-muted {
color: #6c757d; /* Muted text color for unselected star descriptions */
}
.color-1 {
color: #007bff;
}
/* Blue */
.color-2 {
color: #28a745;
}
/* Green */
.color-3 {
color: #ffc107;
}
/* Yellow */
.color-4 {
color: #dc3545;
}
/* Red */
.color-5 {
color: #17a2b8;
}
.color-6 {
color: #12b8;
}
.color-7 {
color: #d92683;
}
.color-8 {
color: #f181b6;
}
.color-8 {
color: #035b19;
}
.grayscale {
filter: grayscale(100%); /* Grays out the image */
}
.selected-image-card {
border: 3px solid #28a745; /* Bootstrap 'success' green color */
position: relative; /* Needed for absolute positioning of the check icon */
}
.selected-image-card::after {
content: "\2713"; /* Unicode character for a check mark */
font-family: "Bootstrap Icons"; /* Ensure this matches the icon font being used */
color: #28a745; /* Match the border color or choose another */
font-size: 24px; /* Adjust size as needed */
position: absolute;
top: 191px;
right: 67px;
z-index: 1;
}
/* Ensure the cards are not too stretched vertically */
.card-img-top {
width: auto;
max-height: 150px; /* Adjust the max-height as needed */
object-fit: cover; /* This will cover the area without stretching */
}
.slider {
-webkit-appearance: none; /* Override default appearance */
width: 100%; /* Slider takes up full container width */
height: 15px; /* Slider thickness */
border-radius: 5px; /* Roundness of the slider */
background: #d3d3d3; /* Background of the slider */
outline: none; /* Removes the outline */
opacity: 0.7; /* Slider opacity */
-webkit-transition: .2s; /* Smooth transitions */
transition: opacity .2s;
}
.slider:disabled {
background: #e9ecef; /* Light grey background when slider is disabled */
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none; /* Removes style of default thumb */
width: 25px; /* Thumb width */
height: 25px; /* Thumb height */
border-radius: 50%; /* Makes thumb circular */
background: #007bff; /* Thumb color */
cursor: pointer; /* Cursor appears as pointer */
}
.slider::-moz-range-thumb {
width: 25px; /* Thumb width for Mozilla */
height: 25px; /* Thumb height for Mozilla */
border-radius: 50%; /* Makes thumb circular for Mozilla */
background: #007bff; /* Thumb color for Mozilla */
cursor: pointer; /* Cursor appears as pointer for Mozilla */
}
#Errocard {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: row;
word-wrap: break-word;
box-shadow: 0px 0px 8px -1px rgba(20,101,230,1);
-webkit-box-shadow: 0px 0px 8px -1px rgba(20,101,230,1);
border-radius: 10px;
background-color: transparent;
height: auto;
flex-wrap: nowrap;
align-items: center;
padding: 0 50px 0 50px;
color:white;
}
.card{
margin-top:40px !important;
}
main{
background-attachment:fixed;
background-repeat: no-repeat;
background: linear-gradient(119deg, rgba(47, 82, 131, 1) 0%, rgba(29, 33, 59, 1) 34%, rgba(27, 54, 61, 1) 67%, rgba(58, 82, 116, 1) 100%) !important;
}
.card-header {
padding: 0.75rem 1.25rem;
margin-bottom: 0;
background-color: rgb(66 122 207 / 44%);
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
}
.badge {
display: inline-block;
padding: 0.25em 0.4em;
font-size: 116%;
font-weight: 700;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: 0.25rem;
</style>
@{
int numberindex = 0;
}
<div class="container pt-5">
@* <h2>Questionnaire: @Model.Questionnaire.Title</h2> *@
<span class="text-white badge badge-success display-3">Response Details for @Model.UserName (@Model.UserEmail)</span>
<h5 class="text-success">Submitted on: <span id="localTime">@Model.SubmissionDate.ToString("yyyy-MM-ddTHH:mm:ss")</span></h5>
<script>
document.addEventListener('DOMContentLoaded', function () {
var utcDate = document.getElementById('localTime').innerText;
var localDate = new Date(utcDate + 'Z'); // Add 'Z' to indicate UTC time
document.getElementById('localTime').innerText = localDate.toLocaleString();
});
</script>
@foreach (var detail in Model.ResponseDetails)
{
<div class="card mb-3" id="Errocard">
<div class="card-header">
Question Type:@detail.QuestionType.ToString()
</div>
<div class="card-body">
<h6 class="card-title"><span class="font-weight-bold">Question @((numberindex = numberindex + 1)):</span> @detail.Question.Text</h6>
<div class="card-text ml-2">
@switch (detail.QuestionType)
{
case QuestionType.Text:
case QuestionType.Open_ended:
<p><strong>Answer:</strong> @detail.TextResponse</p>
break;
case QuestionType.Multiple_choice:
case QuestionType.CheckBox:
case QuestionType.TrueFalse:
<strong>Answer:</strong>
@if (detail.Question.Type == QuestionType.TrueFalse)
{
<!-- Use radio buttons for True/False questions -->
<div class="form-group">
@foreach (var answer in detail.Question.Answers)
{
// Ensuring each question has a unique group name by using the Question ID
string groupName = $"response{detail.Question.Id}";
<div class="form-check">
<input class="form-check-input" type="radio"
name="@groupName"
id="radio-@answer.Id"
value="@answer.Id"
disabled @(detail.ResponseAnswers.Any(ra => ra.AnswerId == answer.Id) ? "checked" : "")>
<label class="form-check-label @(detail.ResponseAnswers.Any(ra => ra.AnswerId == answer.Id) ? "font-weight-bold text-primary" : "text-muted")"
for="radio-@answer.Id">
@answer.Text
</label>
</div>
}
</div>
}
else
{
<!-- Use checkboxes for Multiple Choice and Checkbox questions -->
<div class="form-group">
@foreach (var answer in detail.Question.Answers)
{
<div class="form-check">
<input class="form-check-input" type="checkbox" name="response@answer.Id"
id="checkbox-@answer.Id" value="@answer.Id"
disabled @(detail.ResponseAnswers.Any(ra => ra.AnswerId == answer.Id) ? "checked" : "")>
<label class="form-check-label @(detail.ResponseAnswers.Any(ra => ra.AnswerId == answer.Id) ? "font-weight-bold text-primary" : "text-muted")"
for="checkbox-@answer.Id">
@answer.Text
</label>
</div>
}
</div>
}
break;
case QuestionType.Rating:
@if (detail.QuestionType == QuestionType.Rating)
{
<strong>User Selected rate</strong>
<ul class="list-inline">
@foreach (var answer in detail.Question.Answers.OrderBy(a => a.Id))
{
<li class="list-inline-item p-3">
<!-- Display answer text above the star -->
<div class="@(detail.ResponseAnswers.Any(ra => ra.AnswerId == answer.Id) ? "text-primary" : "text-muted")">
<span class="d-flex justify-content-center">@answer.Text</span>
</div>
<!-- Display star icon -->
<span class="@(detail.ResponseAnswers.Any(ra => ra.AnswerId == answer.Id) ? "text-primary fw-bold fs-3" : "text-secondary")">
<i class="bi bi-star-fill"></i> <!-- Star icon -->
</span>
</li>
}
</ul>
}
break;
case QuestionType.Likert:
<strong> Answer:</strong>
<ul class="list-unstyled m-3">
@foreach (var answer in detail.Question.Answers.OrderBy(a => a.Id))
{
// Construct a unique name for each question-answer pair to ensure radio buttons are not linked.
var radioName = $"likertResponse{detail.Question.Id}-{answer.Id}";
var isAnswerSelected = detail.ResponseAnswers.Any(ra => ra.AnswerId == answer.Id);
<li>
<label class=" @(isAnswerSelected ? "font-weight-bold text-primary" : "text-muted")">
<input type="radio"
class="form-check-input"
name="@radioName"
value="@answer.Id"
disabled
@(isAnswerSelected ? "checked='checked'" : "") />
@answer.Text
</label>
</li>
}
</ul>
break;
case QuestionType.Matrix:
<table class="table">
<strong> Answer:</strong>
@* <thead>
<tr>
<th>User response</th>
</tr>
</thead> *@
<tbody>
@foreach (var answer in detail.Question.Answers)
{
var isSelected = detail.ResponseAnswers.Any(ra => ra.AnswerId == answer.Id);
<tr>
<td class="@(isSelected ? "font-weight-bold text-primary" : "text-muted")">
@answer.Text
</td>
<td>
<input type="radio" class="form-check-input" name="@($"response{detail.Question.Id}_{answer.Id}")" value="@answer.Id"
disabled @(isSelected ? "checked='checked'" : "") />
</td>
</tr>
}
</tbody>
</table>
break;
case QuestionType.Ranking:
<div class="row">
<!-- Original Order of Answers -->
<div class="col-md-6">
<strong>Original Order of Answers:</strong>
<ul class="list-group mb-3">
@foreach (var answer in detail.Question.Answers)
{
var userIndex = detail.ResponseAnswers.FindIndex(ra => ra.AnswerId == answer.Id) + 1;
<li class="list-group-item @($"color-{userIndex}")">
<span class="badge text-bg-secondary badge-pill">@userIndex</span>
@answer.Text
</li>
}
</ul>
</div>
<!-- User Ranked Order -->
<div class="col-md-6">
<strong>User Order of answers:</strong>
<ul class="list-group">
@for (int i = 0; i < detail.ResponseAnswers.Count; i++)
{
var ra = detail.ResponseAnswers[i];
var answer = detail.Question.Answers.FirstOrDefault(a => a.Id == ra.AnswerId);
<li class="list-group-item @($"color-{i+1}")">
<span class="badge text-bg-secondary badge-pill">@(i + 1)</span>
@answer.Text
</li>
}
</ul>
</div>
</div>
break;
case QuestionType.Image:
<div class="container">
<div class="row">
<!-- All Images -->
<div class="col-md-12">
<strong>answers:</strong>
<div class="row">
@foreach (var answer in detail.Question.Answers)
{
var isSelected = detail.ResponseAnswers.Any(ra => ra.AnswerId == answer.Id);
<div class="col-md-3 col-sm-4 col-6">
<!-- Adjust column sizes for various breakpoints -->
<div class="card mb-3 @(isSelected ? "selected-image-card" : "grayscale")">
<img src="@answer.Text" class="card-img-top" alt="@answer.Text">
<div class="card-body">
<a class="btn btn-primary btn-sm btn-block" href="@answer.Text" target="_blank">
View Image
</a>
</div>
</div>
</div>
}
</div>
</div>
</div>
</div>
break;
case QuestionType.Slider:
<div class="container">
<div class="row">
<!-- Slider Responses -->
<div class="col-md-12">
<strong>Answer</strong>
<ul class="list-group mb-3">
@foreach (var answer in detail.Question.Answers)
{
// Retrieve the response that corresponds to the current answer
var responseAnswer = detail.ResponseAnswers.FirstOrDefault(ra => ra.AnswerId == answer.Id);
// Parse the response text to an integer
int sliderValue = 0;
Int32.TryParse(responseAnswer?.ResponseDetail.TextResponse, out sliderValue);
<li class="list-group-item">
<label>@answer.Text</label>
<div class="d-flex align-items-center">
<span class="badge badge-secondary mr-2">0</span> <!-- Min value label -->
<input type="range" min="0" max="100"
value="@sliderValue" class="slider" disabled>
<span class="badge badge-secondary ml-2">100</span> <!-- Max value label -->
</div>
<div class="mt-2">
<span class="badge badge-primary badge-pill">User Response: @sliderValue</span>
</div>
</li>
}
</ul>
</div>
</div>
</div>
break;
// Handle other question types as previously detailed
// Other cases as previously detailed
default:
<p>Unsupported question type.</p>
break;
}
</div>
</div>
</div>
}
<div>
<a asp-action="Index">Back to List</a>
</div>
</div>

View file

@ -36,9 +36,43 @@ namespace Web.Controllers
return View();
}
public IActionResult DisplayQuestionnaire(int id)
public IActionResult DisplayQuestionnaire(int id,string t)
{
// 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
var questionnaire = _questionnaireRepository.GetQuestionnaireWithQuestionAndAnswer(id);
return View(MapToViewModel(questionnaire));
@ -46,48 +80,49 @@ namespace Web.Controllers
[HttpPost]
public IActionResult DisplayQuestionnaire([FromForm] ResponseQuestionnaireViewModel questionnaire)
{
//for (int i = 0; i < questionnaire.Questions.Count; i++)
//bool hasSubmitted = _context.Responses.Any(r => r.QuestionnaireId == questionnaire.Id && r.UserEmail == questionnaire.Email);
//if (hasSubmitted)
//{
// var question = questionnaire.Questions[i];
// List<string> selectedTexts = new List<string>();
// // Assuming SelectedAnswerIds and SelectedTexts are parallel arrays
// for (int j = 0; j < question.SelectedAnswerIds.Count; j++)
// {
// int selectedId = question.SelectedAnswerIds[j];
// if (question.SelectedAnswerIds.Contains(selectedId)) // Ensure the ID was actually selected
// {
// selectedTexts.Add(question.SelectedText[j]);
// Console.WriteLine($"Selected Text{selectedTexts}")
// }
// }
// question.SelectedText = selectedTexts; // Now contains only the texts of selected answers
// TempData["ErrorMessage"] = "You have already completed this survey.";
// return RedirectToAction("ThankYou");
//}
// Process the updated model further as needed
/* return Json(questionnaire);*/ // Redirect to a results view, or handle as necessary
foreach (var question in questionnaire.Questions)
var response = new Response
{
var dbQuestion = _context.Questions.Include(q => q.Answers).FirstOrDefault(q => q.Id == question.Id);
if (dbQuestion != null)
QuestionnaireId = questionnaire.Id,
UserName = questionnaire.UserName,
UserEmail = questionnaire.Email,
SubmissionDate = DateTime.UtcNow,
ResponseDetails = questionnaire.Questions.Select(q => new ResponseDetail
{
foreach (var selectedId in question.SelectedAnswerIds)
{
var selectedAnswer = dbQuestion.Answers.FirstOrDefault(a => a.Id == selectedId);
if (selectedAnswer != null)
{
Console.WriteLine($"Selected Answer Text: {selectedAnswer.Text}");
Console.WriteLine($"Selected Answer Id: {selectedAnswer.Id}");
// Here you could further process each selected answer, e.g., saving user responses
}
}
}
}
return Json(questionnaire);
QuestionId = q.Id,
QuestionType=q.Type,
// Handle TextResponse based on question type
TextResponse = (q.Type == QuestionType.Open_ended || q.Type == QuestionType.Text || q.Type==QuestionType.Slider)
? string.Join(" ", q.SelectedText) // Ensure SelectedText is appropriately used based on question type
: null,
ResponseAnswers = q.SelectedAnswerIds
.Select(aid => new ResponseAnswer { AnswerId = aid })
.ToList() // Ensure that the list is initialized correctly
}).ToList()
};
_context.Responses.Add(response);
_context.SaveChanges();
TempData["UserName"] = questionnaire.UserName;
return RedirectToAction(nameof(ThankYou));
}
[HttpGet]
public IActionResult ThankYou()
{
ViewBag.UserName = TempData["UserName"];
return View();
}
@ -121,41 +156,7 @@ namespace Web.Controllers
}
//// 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

@ -1,368 +0,0 @@
// <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("20240327153248_DataBaseCreated")]
partial class DataBaseCreated
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
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<int>("QuestionId")
.HasColumnType("int");
b.Property<string>("Text")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.ToTable("Answers");
});
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.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.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.Footer", b =>
{
b.Navigation("FooterSocialMedias");
});
modelBuilder.Entity("Model.Question", b =>
{
b.Navigation("Answers");
});
modelBuilder.Entity("Model.Questionnaire", b =>
{
b.Navigation("Questions");
});
modelBuilder.Entity("Model.SocialMedia", b =>
{
b.Navigation("FooterSocialMedias");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -1,36 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Web.Migrations
{
/// <inheritdoc />
public partial class SubscriptionModelAdded : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Subscriptions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
Email = table.Column<string>(type: "nvarchar(max)", nullable: false),
IsSubscribed = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Subscriptions", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Subscriptions");
}
}
}

View file

@ -1,391 +0,0 @@
// <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("20240403083337_RequriedfiledRemoved")]
partial class RequriedfiledRemoved
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.2")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
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<int>("QuestionId")
.HasColumnType("int");
b.Property<string>("Text")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.ToTable("Answers");
});
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.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("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.Footer", b =>
{
b.Navigation("FooterSocialMedias");
});
modelBuilder.Entity("Model.Question", b =>
{
b.Navigation("Answers");
});
modelBuilder.Entity("Model.Questionnaire", b =>
{
b.Navigation("Questions");
});
modelBuilder.Entity("Model.SocialMedia", b =>
{
b.Navigation("FooterSocialMedias");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -1,36 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Web.Migrations
{
/// <inheritdoc />
public partial class RequriedfiledRemoved : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Subscriptions",
type: "nvarchar(max)",
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Subscriptions",
type: "nvarchar(max)",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldNullable: true);
}
}
}

View file

@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Web.Migrations
{
[DbContext(typeof(SurveyContext))]
[Migration("20240401115718_SubscriptionModelAdded")]
partial class SubscriptionModelAdded
[Migration("20240424144801_initial")]
partial class initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -262,6 +262,83 @@ namespace Web.Migrations
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>("TextResponse")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.HasIndex("ResponseId");
b.ToTable("ResponseDetails");
});
modelBuilder.Entity("Model.SocialMedia", b =>
{
b.Property<int>("Id")
@ -299,7 +376,6 @@ namespace Web.Migrations
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
@ -367,6 +443,47 @@ namespace Web.Migrations
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");
@ -382,6 +499,16 @@ namespace Web.Migrations
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");

View file

@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace Web.Migrations
{
/// <inheritdoc />
public partial class DataBaseCreated : Migration
public partial class initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@ -97,6 +97,21 @@ namespace Web.Migrations
table.PrimaryKey("PK_SocialMedia", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Subscriptions",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(max)", nullable: true),
Email = table.Column<string>(type: "nvarchar(max)", nullable: false),
IsSubscribed = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Subscriptions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Pages",
columns: table => new
@ -147,6 +162,28 @@ namespace Web.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Responses",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
QuestionnaireId = table.Column<int>(type: "int", nullable: false),
UserName = table.Column<string>(type: "nvarchar(max)", nullable: true),
UserEmail = table.Column<string>(type: "nvarchar(max)", nullable: true),
SubmissionDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Responses", x => x.Id);
table.ForeignKey(
name: "FK_Responses_Questionnaires_QuestionnaireId",
column: x => x.QuestionnaireId,
principalTable: "Questionnaires",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "FooterSocialMedias",
columns: table => new
@ -191,6 +228,54 @@ namespace Web.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ResponseDetails",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ResponseId = table.Column<int>(type: "int", nullable: false),
QuestionId = table.Column<int>(type: "int", nullable: false),
QuestionType = table.Column<int>(type: "int", nullable: false),
TextResponse = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ResponseDetails", x => x.Id);
table.ForeignKey(
name: "FK_ResponseDetails_Questions_QuestionId",
column: x => x.QuestionId,
principalTable: "Questions",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_ResponseDetails_Responses_ResponseId",
column: x => x.ResponseId,
principalTable: "Responses",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ResponseAnswers",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ResponseDetailId = table.Column<int>(type: "int", nullable: false),
AnswerId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ResponseAnswers", x => x.Id);
table.ForeignKey(
name: "FK_ResponseAnswers_ResponseDetails_ResponseDetailId",
column: x => x.ResponseDetailId,
principalTable: "ResponseDetails",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Answers_QuestionId",
table: "Answers",
@ -215,6 +300,26 @@ namespace Web.Migrations
name: "IX_Questions_QuestionnaireId",
table: "Questions",
column: "QuestionnaireId");
migrationBuilder.CreateIndex(
name: "IX_ResponseAnswers_ResponseDetailId",
table: "ResponseAnswers",
column: "ResponseDetailId");
migrationBuilder.CreateIndex(
name: "IX_ResponseDetails_QuestionId",
table: "ResponseDetails",
column: "QuestionId");
migrationBuilder.CreateIndex(
name: "IX_ResponseDetails_ResponseId",
table: "ResponseDetails",
column: "ResponseId");
migrationBuilder.CreateIndex(
name: "IX_Responses_QuestionnaireId",
table: "Responses",
column: "QuestionnaireId");
}
/// <inheritdoc />
@ -233,7 +338,10 @@ namespace Web.Migrations
name: "Pages");
migrationBuilder.DropTable(
name: "Questions");
name: "ResponseAnswers");
migrationBuilder.DropTable(
name: "Subscriptions");
migrationBuilder.DropTable(
name: "SocialMedia");
@ -244,6 +352,15 @@ namespace Web.Migrations
migrationBuilder.DropTable(
name: "Footers");
migrationBuilder.DropTable(
name: "ResponseDetails");
migrationBuilder.DropTable(
name: "Questions");
migrationBuilder.DropTable(
name: "Responses");
migrationBuilder.DropTable(
name: "Questionnaires");
}

View file

@ -259,6 +259,83 @@ namespace Web.Migrations
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>("TextResponse")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.HasIndex("ResponseId");
b.ToTable("ResponseDetails");
});
modelBuilder.Entity("Model.SocialMedia", b =>
{
b.Property<int>("Id")
@ -363,6 +440,47 @@ namespace Web.Migrations
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");
@ -378,6 +496,16 @@ namespace Web.Migrations
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");

View file

@ -51,6 +51,8 @@ if (!app.Environment.IsDevelopment())
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();

View file

@ -1,4 +1,5 @@
using Web.ViewModel.QuestionVM;
using System.ComponentModel.DataAnnotations;
using Web.ViewModel.QuestionVM;
namespace Web.ViewModel.QuestionnaireVM
{
@ -9,8 +10,15 @@ namespace Web.ViewModel.QuestionnaireVM
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>();
[Required]
public string? UserName { get; set; }
[Required]
public string? Email { get; set; }
// Collection of questions
public List<ResponseQuestionViewModel> Questions { get; set; } = new List<ResponseQuestionViewModel>();
}

View file

@ -33,7 +33,7 @@
}
.QuestionContainer {
padding-top: 100px;
padding-top: 50px;
padding-bottom: 100px;
}
@ -320,9 +320,14 @@
<h4>@Model.Title</h4>
<p>@Html.Raw(Model.Description)</p>
<div class="form-group">
<label for="userName">Your Name:</label>
<input type="text" class="form-control" id="userName" name="UserName" placeholder="Enter your name">
</div>
<div class="form-group">
<label for="Email">Email Address:</label>
<input type="email" class="form-control" id="Email" name="Email" placeholder="Enter your email">
</div>
<div class="container">
<div class="row align-items-center">
<!-- Stepper -->
@ -341,6 +346,7 @@
</div>
<!-- Form Content -->
<div class="col-md-9">
@for (int i = 0; i < Model.Questions.Count; i++)
{
var question = Model.Questions[i];
@ -359,7 +365,7 @@
<p class="font-weight-normal">@(i + 1). @question.Text</p>
@switch (question.Type)
{
@ -368,7 +374,7 @@
{
<input type="Text" class="form-control" id="question@(i + 1)" name="Questions[@i].SelectedText" rows="3" placeholder="Enter answer"></input>
<input class="form-control hidden-textarea" id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" rows="3" placeholder="Enter answer"></input>
<input class="form-control hidden-textarea" id="question@(i + 1)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" rows="3" placeholder="Enter answer" ></input>
}
break;
case QuestionType.CheckBox:
@ -380,7 +386,7 @@
@foreach (var answer in question.Answers)
{
<div class="form-check">
<input class="form-check-input" id="question@(i)_answer@(answer.Id)" type="checkbox" name="Questions[@i].SelectedAnswerIds" value="@answer.Id">
<input class="form-check-input" id="question@(i)_answer@(answer.Id)" type="checkbox" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" >
<label class="form-check-label" for="question@(i)_answer@(answer.Id)">
@answer.Text
</label>
@ -625,7 +631,7 @@
</div>
break;
}
<div class="mt-3">
@if (i > 0)
{
@ -638,7 +644,16 @@
</div>
</div>
}
<div class="User-details" style="display:none;">
<div class="form-group">
<label for="userName">Your Name:</label>
<input type="text" class="form-control" id="userName" name="UserName" placeholder="Enter your name" >
</div>
<div class="form-group">
<label for="userEmail">Email Address:</label>
<input type="email" class="form-control" id="userEmail" name="UserEmail" placeholder="Enter your email" >
</div>
</div>
<button type="submit" class="btn btn-primary submit btn-sm mt-4" id="BannerButon">Submit</button>
</div>
@ -799,6 +814,11 @@
showStep(currentStep);
});
// document.addEventListener("DOMContentLoaded", function () {
// const stepper = document.querySelector('.stepper');
// if (!stepper) {

View file

@ -167,29 +167,6 @@
@* <div class="containerError">
<div class="container">
<div class="row">
<div class="card">
<!-- Stepper -->
<div class="col-md-6">
<h5 class="text-danger">@ViewBag.ErrorMessage</h5>
<a href="#" class="btn btn-sm mt-3" id="BannerButon"> Contact <i class="bi bi-envelope-at-fill"></i></a>
</div>
<!-- Form Content -->
<div class="col-md-6">
<script src="https://unpkg.com/@@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
<dotlottie-player src="https://lottie.host/e3084d59-5df1-44e9-a0fe-0a015257fba7/LVAJX2PbZo.json" class="img-fluid" speed="1" style="width: auto; height: auto;" direction="1" playMode="normal" loop autoplay></dotlottie-player>
</div>
</div>
</div>
</div>
</div> *@

View file

@ -0,0 +1,164 @@

@{
ViewData["Title"] = "Thank You";
Layout = "~/Views/Shared/_QuestionnaireResponse.cshtml";
}
<style>
body {
height: 100% !important;
background-repeat: no-repeat;
background: linear-gradient(119deg, rgba(47,82,131,1) 0%, rgba(29,33,59,1) 34%, rgba(27,54,61,1) 67%, rgba(58,82,116,1) 100%) !important
}
#rowSectionError {
display: flex;
flex-wrap: nowrap;
justify-content: space-around;
align-items: center;
align-content: center;
width: 100%;
flex-direction: row;
}
#Errocard {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: row;
word-wrap: break-word;
box-shadow: 0px 0px 36px -12px rgba(20,101,230,1);
-webkit-box-shadow: 0px 0px 20px 2px rgba(20,101,230,1);
border-radius: 10px;
background-color: transparent;
height: 400px;
flex-wrap: nowrap;
align-items: center;
padding: 0 50px 0 50px;
margin: 150px 30px 0px 30px;
}
@@media (max-width: 792px) {
#Errocard {
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: center;
align-items: center;
align-content: center;
height: 70vh;
padding: 0 20px 0 20px;
}
#boxError {
width: auto;
height: 30%;
margin: 5px;
}
}
@@media (max-width: 800px) {
#Errocard {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
align-content: center;
height: 70vh;
padding: 0 20px 0 20px;
}
#boxError {
width: auto;
height: 30%;
margin: 5px;
}
}
@@media (max-width: 1300px) {
#Errocard {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: row;
word-wrap: break-word;
box-shadow: 0px 0px 36px -12px rgba(20,101,230,1);
-webkit-box-shadow: 0px 0px 20px 2px rgba(20,101,230,1);
border-radius: 10px;
background-color: transparent;
height: 400px;
flex-wrap: nowrap;
align-items: center;
padding: 0 50px 0 50px;
margin: 50px 30px 0px 30px;
}
#boxError {
width: auto;
margin: 5px;
}
#boxBanner {
display: block;
width: auto;
margin: 5px;
}
</style>
<div class="d-flex flex-column" id="BannerBackground">
<!-- FOR DEMO PURPOSE -->
<section class="text-white">
<div class="container py-1">
<div class="card" id="Errocard">
<div class="col-lg-7" id="boxError">
<h4 class="text-white">Hey <span class="text-info font-weight-bold">@ViewBag.UserName</span></h4>
<h6 class="text-white">Thank you for taking the time to submit the form! Your input is invaluable to us and will help us improve our services.</h6>
</div>
<div class="col-lg-5" id="boxError">
<script src="https://unpkg.com/@@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
<dotlottie-player src="https://lottie.host/b19b8f6f-debe-408a-99db-1b82713cc8b4/C7LtuHstVr.json" class="img-fluid" speed="1" style="width: 300px; height: 300px;" direction="1" playMode="normal" autoplay></dotlottie-player>
</div>
</div>
</div>
</section>
</div>

View file

@ -15,7 +15,8 @@
"ApplicationName": "Online Survey",
"ConfirmEmailPath": "Subscription/Confirmation",
"unsubscribePath": "Subscription/UnsubscribeConfirmation",
"Questionnaire": "QuestionnaireResponse/DisplayQuestionnaire"
"Questionnaire": "QuestionnaireResponse/DisplayQuestionnaire",
"EncryptionKey": ""
},
"MailJet": {