Survey Analyzer for each survey created
This commit is contained in:
parent
181237e9ff
commit
d3a6fd4918
17 changed files with 700 additions and 66 deletions
|
|
@ -1,10 +1,4 @@
|
||||||
using System;
|
namespace Model
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Model
|
|
||||||
{
|
{
|
||||||
public enum QuestionType
|
public enum QuestionType
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ using Services.Interaces;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Web;
|
||||||
using Web.ViewModel.QuestionnaireVM;
|
using Web.ViewModel.QuestionnaireVM;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -470,6 +471,8 @@ namespace Web.Areas.Admin.Controllers
|
||||||
var questionnairePath = _configuration["Email:Questionnaire"];
|
var questionnairePath = _configuration["Email:Questionnaire"];
|
||||||
int surveyId = viewModel.QuestionnaireId;
|
int surveyId = viewModel.QuestionnaireId;
|
||||||
|
|
||||||
|
var userEmailEncoded = HttpUtility.UrlEncode(viewModel.Email);
|
||||||
|
|
||||||
|
|
||||||
DateTime currentDateTime;
|
DateTime currentDateTime;
|
||||||
if (viewModel.ExpirationDateTime.HasValue)
|
if (viewModel.ExpirationDateTime.HasValue)
|
||||||
|
|
@ -491,9 +494,9 @@ namespace Web.Areas.Admin.Controllers
|
||||||
// Append the expiration date and time to the token (you might want to encrypt it for security)
|
// Append the expiration date and time to the token (you might want to encrypt it for security)
|
||||||
string tokenWithExpiry = $"{token}|{expiryDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ")}";
|
string tokenWithExpiry = $"{token}|{expiryDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ")}";
|
||||||
|
|
||||||
|
var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}?t={tokenWithExpiry}&E={userEmailEncoded}";
|
||||||
|
|
||||||
|
//var completeUrl = $"{Request.Scheme}://{Request.Host}/{questionnairePath}/{viewModel.QuestionnaireId}?t={tokenWithExpiry}&E={userEmail}";
|
||||||
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}";
|
||||||
|
|
||||||
|
|
|
||||||
115
Web/Areas/Admin/Controllers/SurveyAnalysisController.cs
Normal file
115
Web/Areas/Admin/Controllers/SurveyAnalysisController.cs
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
using Azure;
|
||||||
|
using Data;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Model;
|
||||||
|
using Web.ViewModel.QuestionnaireVM;
|
||||||
|
using Web.ViewModel.QuestionVM;
|
||||||
|
|
||||||
|
namespace Web.Areas.Admin.Controllers
|
||||||
|
{
|
||||||
|
public class SurveyAnalysisController : Controller
|
||||||
|
{
|
||||||
|
private readonly SurveyContext _context;
|
||||||
|
|
||||||
|
public SurveyAnalysisController(SurveyContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
var questionnaires = _context.Responses
|
||||||
|
.Include(r => r.Questionnaire) // Ensure the navigation property is correctly set up in the Response model
|
||||||
|
.Select(r => r.Questionnaire)
|
||||||
|
.Distinct() // Ensure each questionnaire is listed once
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var viewModel = questionnaires.Select(q => new ResponseQuestionnaireWithUsersViewModel
|
||||||
|
{
|
||||||
|
Id = q.Id,
|
||||||
|
Title = q.Title
|
||||||
|
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return View(viewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult Analysis(int id)
|
||||||
|
{
|
||||||
|
var viewModel = _context.Responses
|
||||||
|
.Where(r => r.QuestionnaireId == id)
|
||||||
|
.Include(r => r.Questionnaire)
|
||||||
|
.ThenInclude(q => q.Questions)
|
||||||
|
.ThenInclude(q => q.Answers)
|
||||||
|
.Select(r => new ResponseQuestionnaireWithUsersViewModel
|
||||||
|
{
|
||||||
|
Id = r.Questionnaire.Id,
|
||||||
|
Title = r.Questionnaire.Title,
|
||||||
|
Description = r.Questionnaire.Description,
|
||||||
|
UserName = r.UserName, // Assuming you want the user who answered the questionnaire
|
||||||
|
Email = r.UserEmail,
|
||||||
|
ParticipantCount = _context.Responses.Count(rs => rs.QuestionnaireId == id),
|
||||||
|
QuestionsAnsweredPercentage = _context.Questionnaires
|
||||||
|
.Where(q => q.Id == id)
|
||||||
|
.SelectMany(q => q.Questions)
|
||||||
|
.Count() > 0
|
||||||
|
? (double)_context.ResponseDetails
|
||||||
|
.Where(rd => rd.Response.QuestionnaireId == id && rd.TextResponse != null)
|
||||||
|
.Select(rd => rd.QuestionId)
|
||||||
|
.Distinct()
|
||||||
|
.Count() / _context.Questionnaires
|
||||||
|
.Where(q => q.Id == id)
|
||||||
|
.SelectMany(q => q.Questions)
|
||||||
|
.Count() * 100.0
|
||||||
|
: 0.0, // Avoid division by zero
|
||||||
|
Questions = r.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 ?? _context.ResponseDetails
|
||||||
|
.Where(rd => rd.QuestionId == q.Id && rd.ResponseId == r.Id)
|
||||||
|
.Select(rd => rd.TextResponse)
|
||||||
|
.FirstOrDefault(),
|
||||||
|
Count = _context.ResponseAnswers.Count(ra => ra.AnswerId == a.Id) // Count how many times each answer was selected
|
||||||
|
}).ToList(),
|
||||||
|
SelectedAnswerIds = _context.ResponseDetails
|
||||||
|
.Where(rd => rd.QuestionId == q.Id)
|
||||||
|
.SelectMany(rd => rd.ResponseAnswers)
|
||||||
|
.Select(ra => ra.AnswerId)
|
||||||
|
.Distinct()
|
||||||
|
.ToList(),
|
||||||
|
SelectedText = _context.ResponseDetails
|
||||||
|
.Where(rd => rd.QuestionId == q.Id)
|
||||||
|
.Select(rd => rd.TextResponse)
|
||||||
|
.Where(t => !string.IsNullOrEmpty(t))
|
||||||
|
.ToList()
|
||||||
|
}).ToList(),
|
||||||
|
Users = _context.Responses
|
||||||
|
.Where(rs => rs.QuestionnaireId == id)
|
||||||
|
.Select(rs => new ResponseUserViewModel
|
||||||
|
{
|
||||||
|
UserName = rs.UserName,
|
||||||
|
Email = rs.UserEmail
|
||||||
|
}).Distinct().ToList()
|
||||||
|
})
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (viewModel == null)
|
||||||
|
{
|
||||||
|
return NotFound("No questionnaire found for the given ID.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return View(viewModel);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -46,5 +46,37 @@ namespace Web.Areas.Admin.Controllers
|
||||||
|
|
||||||
return View(response); // Pass the response to the view
|
return View(response); // Pass the response to the view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Delete(int id)
|
||||||
|
{
|
||||||
|
var response = await _context.Responses.FindAsync(id);
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Responses.Remove(response);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> DeleteMultiple(int[] ids)
|
||||||
|
{
|
||||||
|
var responses = _context.Responses.Where(r => ids.Contains(r.Id));
|
||||||
|
|
||||||
|
_context.Responses.RemoveRange(responses);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
TempData["Success"] = "User response deleted successfully";
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@
|
||||||
|
|
||||||
<a asp-controller="Questionnaire" asp-action="index"><span class="bi bi-question-circle"></span> Survey</a>
|
<a asp-controller="Questionnaire" asp-action="index"><span class="bi bi-question-circle"></span> Survey</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a asp-controller="SurveyAnalysis" asp-action="index"><span class="bi bi-graph-up-arrow"></span> Analyzer</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|
||||||
<a asp-controller="UserResponse" asp-action="index"><span class="bi bi-clipboard-data"></span> Response</a>
|
<a asp-controller="UserResponse" asp-action="index"><span class="bi bi-clipboard-data"></span> Response</a>
|
||||||
|
|
@ -46,6 +49,7 @@
|
||||||
<li>
|
<li>
|
||||||
<a asp-controller="newsletters" asp-action="index"><span class="bi bi-newspaper"></span> Subscibers</a>
|
<a asp-controller="newsletters" asp-action="index"><span class="bi bi-newspaper"></span> Subscibers</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
133
Web/Areas/Admin/Views/SurveyAnalysis/Analysis.cshtml
Normal file
133
Web/Areas/Admin/Views/SurveyAnalysis/Analysis.cshtml
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
@using Newtonsoft.Json
|
||||||
|
@model ResponseQuestionnaireWithUsersViewModel
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Detailed Survey Analysis";
|
||||||
|
}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.progress {
|
||||||
|
position: relative;
|
||||||
|
height: 30px; /* Increased height for better visibility */
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: black; /* Ensures text is visible on light backgrounds */
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 30px; /* Align text vertically */
|
||||||
|
}
|
||||||
|
|
||||||
|
#Errocard{
|
||||||
|
|
||||||
|
|
||||||
|
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: 0px 0px 8px -1px rgba(20,101,230,1);
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.Errocard {
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
rect{
|
||||||
|
|
||||||
|
border-radius:10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container mt-5 p-5">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="card" id="Errocard">
|
||||||
|
<div class="card-header">
|
||||||
|
Survey Analyzer
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title font-weight-bolder ">@Model.Title</h5>
|
||||||
|
<p class="card-text">@Html.Raw(Model.Description)</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<div class="font-weight-bolder">
|
||||||
|
|
||||||
|
Total user respond <span class="badge text-bg-primary">@Model.ParticipantCount</span><br />
|
||||||
|
|
||||||
|
Users
|
||||||
|
@foreach (var item in Model.Users)
|
||||||
|
{
|
||||||
|
<span class="badge text-bg-primary">@item.UserName</span>
|
||||||
|
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5" >
|
||||||
|
@foreach (var question in Model.Questions)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
|
||||||
|
<div id="chart_div_@question.Id" class="Errocard m-4" style="width: 1000px; height: 300px;"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
google.charts.load('current', { 'packages': ['corechart'] });
|
||||||
|
google.charts.setOnLoadCallback(drawCharts);
|
||||||
|
|
||||||
|
function drawCharts() {
|
||||||
|
@foreach (var question in Model.Questions)
|
||||||
|
{
|
||||||
|
<text>
|
||||||
|
var data = new google.visualization.DataTable();
|
||||||
|
data.addColumn('string', 'Option');
|
||||||
|
data.addColumn('number', 'Count');
|
||||||
|
data.addRows([
|
||||||
|
@foreach (var answer in question.Answers)
|
||||||
|
{
|
||||||
|
<text>['@Html.Raw(answer.Text)', @answer.Count], </text>
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Set chart options
|
||||||
|
var options = {
|
||||||
|
'title': '@Html.Raw(question.Text)',
|
||||||
|
is3D: true,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Select the right container for the chart
|
||||||
|
var container = document.getElementById('chart_div_@question.Id');
|
||||||
|
if (container) {
|
||||||
|
var chart = new google.visualization.PieChart(container);
|
||||||
|
chart.draw(data, options);
|
||||||
|
}
|
||||||
|
</text>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
64
Web/Areas/Admin/Views/SurveyAnalysis/Index.cshtml
Normal file
64
Web/Areas/Admin/Views/SurveyAnalysis/Index.cshtml
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
@model IEnumerable<ResponseQuestionnaireWithUsersViewModel>
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Survey Analysis";
|
||||||
|
}
|
||||||
|
<div class="container mt-5">
|
||||||
|
|
||||||
|
<partial name="_Notification" />
|
||||||
|
|
||||||
|
|
||||||
|
<div class="card bg-default mb-3 ">
|
||||||
|
<div class="card-header">Survey analysis</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">Survey analysis list</h4>
|
||||||
|
|
||||||
|
|
||||||
|
<form asp-action="DeleteMultiple" method="post">
|
||||||
|
|
||||||
|
<table class="table table-responsive w-100 d-block d-md-table ">
|
||||||
|
<thead class="w-100">
|
||||||
|
<tr>
|
||||||
|
<th><input type="checkbox" onclick="selectAll(this)" /></th> <!-- Master checkbox -->
|
||||||
|
<th>Id</th>
|
||||||
|
<th>Questionnaire</th>
|
||||||
|
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="w-100">
|
||||||
|
@foreach (var item in Model)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox" name="ids" value="@item.Id" /></td>
|
||||||
|
<td>@item.Id</td>
|
||||||
|
<td>@item.Title</td>
|
||||||
|
|
||||||
|
<td class="">
|
||||||
|
<a asp-action="Analysis" asp-route-id="@item.Id" class="btn btn-warning btn-sm">Details</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-danger">Delete Selected</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
function selectAll(source) {
|
||||||
|
checkboxes = document.getElementsByName('ids');
|
||||||
|
for (var i = 0, n = checkboxes.length; i < n; i++) {
|
||||||
|
checkboxes[i].checked = source.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -9,48 +9,62 @@
|
||||||
|
|
||||||
<partial name="_Notification" />
|
<partial name="_Notification" />
|
||||||
|
|
||||||
|
|
||||||
<div class="card bg-default mb-3 ">
|
<div class="card bg-default mb-3 ">
|
||||||
<div class="card-header">User Reponse</div>
|
<div class="card-header">User Reponse</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="card-title">User response list</h4>
|
<h4 class="card-title">User response list</h4>
|
||||||
|
|
||||||
|
|
||||||
|
<form asp-action="DeleteMultiple" method="post">
|
||||||
|
|
||||||
<table class="table table-responsive w-100 d-block d-md-table ">
|
<table class="table table-responsive w-100 d-block d-md-table ">
|
||||||
<thead class="w-auto">
|
<thead class="w-100">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th><input type="checkbox" onclick="selectAll(this)" /></th> <!-- Master checkbox -->
|
||||||
<th scope="col">Id</th>
|
<th>Id</th>
|
||||||
|
<th>Questionnaire</th>
|
||||||
<th scope="col">Questionnaire</th>
|
<th>UserName</th>
|
||||||
<th scope="col">UserName</th>
|
<th>UserEmail</th>
|
||||||
<th scope="col">UserEmail</th>
|
<th>Submission Date</th>
|
||||||
<th scope="col">Submission Date</th>
|
<th>Action</th>
|
||||||
<th scope="col" class="d-flex justify-content-end">Action</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="w-auto">
|
<tbody class="w-100">
|
||||||
@foreach (var item in Model)
|
@foreach (var item in Model)
|
||||||
{
|
{
|
||||||
<tr class="table-secondary">
|
<tr>
|
||||||
|
<td><input type="checkbox" name="ids" value="@item.Id" /></td>
|
||||||
<td>@item.Id</td>
|
<td>@item.Id</td>
|
||||||
<td> <span class="badge bg-primary">@item.Questionnaire.Title</span></td>
|
<td>@item.Questionnaire.Title</td>
|
||||||
<td>@item.UserName</td>
|
|
||||||
<td>@item.UserName</td>
|
<td>@item.UserName</td>
|
||||||
|
<td>@item.UserEmail</td>
|
||||||
<td>@item.SubmissionDate</td>
|
<td>@item.SubmissionDate</td>
|
||||||
<td class="d-flex justify-content-end">
|
<td>
|
||||||
<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">Details</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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-danger">Delete Selected</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@section Scripts{
|
||||||
|
<script>
|
||||||
|
function selectAll(source) {
|
||||||
|
checkboxes = document.getElementsByName('ids');
|
||||||
|
for (var i = 0, n = checkboxes.length; i < n; i++) {
|
||||||
|
checkboxes[i].checked = source.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.star-rating .text-primary {
|
.star-rating .text-primary {
|
||||||
color:goldenrod; /* Bootstrap primary blue */
|
color:goldenrod; /* Bootstrap primary blue */
|
||||||
font-weight: bold; /* Make text bold */
|
font-weight: bold; /* Make text bold */
|
||||||
|
|
@ -182,6 +180,7 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
@ -192,9 +191,8 @@
|
||||||
|
|
||||||
<div class="container pt-5">
|
<div class="container pt-5">
|
||||||
@* <h2>Questionnaire: @Model.Questionnaire.Title</h2> *@
|
@* <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-white">Response Details for @Model.UserName (@Model.UserEmail)</h5>
|
||||||
<h5 class="text-success">Submitted on: <span id="localTime">@Model.SubmissionDate.ToString("yyyy-MM-ddTHH:mm:ss")</span></h5>
|
<h5 class="text-success">Submitted on: <span id="localTime">@Model.SubmissionDate.ToString("yyyy-MM-ddTHH:mm:ss")</span></h5>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var utcDate = document.getElementById('localTime').innerText;
|
var utcDate = document.getElementById('localTime').innerText;
|
||||||
|
|
@ -203,7 +201,6 @@
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@foreach (var detail in Model.ResponseDetails)
|
@foreach (var detail in Model.ResponseDetails)
|
||||||
{
|
{
|
||||||
<div class="card mb-3" id="Errocard">
|
<div class="card mb-3" id="Errocard">
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
using Data;
|
using Data;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
|
||||||
using Model;
|
using Model;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Services.EmailSend;
|
||||||
using Services.Interaces;
|
using Services.Interaces;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
@ -17,11 +19,13 @@ namespace Web.Controllers
|
||||||
{
|
{
|
||||||
private readonly IQuestionnaireRepository _questionnaireRepository;
|
private readonly IQuestionnaireRepository _questionnaireRepository;
|
||||||
private readonly SurveyContext _context;
|
private readonly SurveyContext _context;
|
||||||
|
private readonly IEmailServices _emailServices;
|
||||||
|
|
||||||
public QuestionnaireResponseController(IQuestionnaireRepository questionnaireRepository,SurveyContext context)
|
public QuestionnaireResponseController(IQuestionnaireRepository questionnaireRepository,SurveyContext context, IEmailServices emailServices)
|
||||||
{
|
{
|
||||||
_questionnaireRepository = questionnaireRepository;
|
_questionnaireRepository = questionnaireRepository;
|
||||||
_context = context;
|
_context = context;
|
||||||
|
_emailServices = emailServices;
|
||||||
}
|
}
|
||||||
public IActionResult Index()
|
public IActionResult Index()
|
||||||
{
|
{
|
||||||
|
|
@ -36,7 +40,7 @@ namespace Web.Controllers
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult DisplayQuestionnaire(int id,string t)
|
public IActionResult DisplayQuestionnaire(int id,string t,string E)
|
||||||
{
|
{
|
||||||
// Check if the token is null or empty
|
// Check if the token is null or empty
|
||||||
if (string.IsNullOrEmpty(t))
|
if (string.IsNullOrEmpty(t))
|
||||||
|
|
@ -72,6 +76,29 @@ namespace Web.Controllers
|
||||||
return RedirectToAction(nameof(Error));
|
return RedirectToAction(nameof(Error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasAlreadyResponded = _context.Responses.Any(r => r.QuestionnaireId == id && r.UserEmail == E);
|
||||||
|
if (hasAlreadyResponded)
|
||||||
|
{
|
||||||
|
// Retrieve the first username associated with the email, if available
|
||||||
|
var userName = _context.Responses.Where(x => x.UserEmail == E)
|
||||||
|
.Select(x => x.UserName)
|
||||||
|
.FirstOrDefault(); // This ensures you get a single result or null
|
||||||
|
|
||||||
|
// Ensure userName is not null or empty to use in the message
|
||||||
|
if (!string.IsNullOrEmpty(userName))
|
||||||
|
{
|
||||||
|
TempData["ErrorMessage"] = $"{userName}";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TempData["ErrorMessage"] = "You have already taken this survey.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(SubmittedSurvey));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Retrieve the questionnaire using the numeric ID
|
// Retrieve the questionnaire using the numeric ID
|
||||||
var questionnaire = _questionnaireRepository.GetQuestionnaireWithQuestionAndAnswer(id);
|
var questionnaire = _questionnaireRepository.GetQuestionnaireWithQuestionAndAnswer(id);
|
||||||
|
|
||||||
|
|
@ -80,13 +107,9 @@ namespace Web.Controllers
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult DisplayQuestionnaire([FromForm] ResponseQuestionnaireViewModel questionnaire)
|
public IActionResult DisplayQuestionnaire([FromForm] ResponseQuestionnaireViewModel questionnaire)
|
||||||
{
|
{
|
||||||
//bool hasSubmitted = _context.Responses.Any(r => r.QuestionnaireId == questionnaire.Id && r.UserEmail == questionnaire.Email);
|
bool hasSubmitted = _context.Responses.Any(r => r.QuestionnaireId == questionnaire.Id && r.UserEmail == questionnaire.Email);
|
||||||
|
|
||||||
|
|
||||||
//if (hasSubmitted)
|
|
||||||
//{
|
|
||||||
// TempData["ErrorMessage"] = "You have already completed this survey.";
|
|
||||||
// return RedirectToAction("ThankYou");
|
|
||||||
//}
|
|
||||||
|
|
||||||
var response = new Response
|
var response = new Response
|
||||||
{
|
{
|
||||||
|
|
@ -111,6 +134,68 @@ namespace Web.Controllers
|
||||||
|
|
||||||
_context.Responses.Add(response);
|
_context.Responses.Add(response);
|
||||||
_context.SaveChanges();
|
_context.SaveChanges();
|
||||||
|
var subject = $"Thank You for Your Feedback, {questionnaire.UserName}!";
|
||||||
|
var toEmail = questionnaire.Email;
|
||||||
|
string emailBody = $@"
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
/* Inline CSS styles */
|
||||||
|
body {{
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.container {{
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 0.5px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}}
|
||||||
|
.button {{
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
}}
|
||||||
|
.button:hover {{
|
||||||
|
background-color: #0056b3;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class='container'>
|
||||||
|
<h4>Hey {questionnaire.UserName.ToUpper()},</h4>
|
||||||
|
<h5>{subject}</h5>
|
||||||
|
<p><strong>Thank you so much for taking the time to provide us with your valuable feedback!</strong></p>
|
||||||
|
|
||||||
|
<p>If you have any more thoughts to share or need assistance, please don't hesitate to reach out. You can email us directly at seo@seosoft.dk, and we'll be more than happy to help.</p>
|
||||||
|
|
||||||
|
<p>Thank you once again, {questionnaire.UserName}, for helping us make SeoSoft ApS even better. We truly value your support and participation.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<p><strong>Søren Eggert Lundsteen Olsen</strong><br>
|
||||||
|
Seosoft ApS<br>
|
||||||
|
<hr>
|
||||||
|
Hovedgaden 3
|
||||||
|
Jordrup<br>
|
||||||
|
Kolding 6064<br>
|
||||||
|
Denmark</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>";
|
||||||
|
|
||||||
|
|
||||||
|
// Call the SendConfirmationEmailAsync method to send the email
|
||||||
|
var emailSend = new EmailToSend(toEmail, subject, emailBody);
|
||||||
|
|
||||||
|
_emailServices.SendConfirmationEmailAsync(emailSend);
|
||||||
|
|
||||||
|
|
||||||
TempData["UserName"] = questionnaire.UserName;
|
TempData["UserName"] = questionnaire.UserName;
|
||||||
|
|
||||||
|
|
@ -125,6 +210,13 @@ namespace Web.Controllers
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult SubmittedSurvey()
|
||||||
|
{
|
||||||
|
ViewBag.submitedEmail = TempData["ErrorMessage"];
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private ResponseQuestionnaireViewModel MapToViewModel(Questionnaire questionnaire)
|
private ResponseQuestionnaireViewModel MapToViewModel(Questionnaire questionnaire)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,7 @@
|
||||||
{
|
{
|
||||||
public int Id { get; set; } // Answer ID
|
public int Id { get; set; } // Answer ID
|
||||||
public string? Text { get; set; } // Answer text
|
public string? Text { get; set; } // Answer text
|
||||||
|
|
||||||
|
public int? Count { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using Web.ViewModel.QuestionVM;
|
|
||||||
|
|
||||||
namespace Web.ViewModel.QuestionnaireVM
|
namespace Web.ViewModel.QuestionnaireVM
|
||||||
{
|
{
|
||||||
|
|
@ -15,9 +15,10 @@ namespace Web.ViewModel.QuestionnaireVM
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string? Email { get; set; }
|
public string? Email { get; set; }
|
||||||
|
public int ParticipantCount { get; set; }
|
||||||
|
public int QuestionsAnsweredCount { get; set; }
|
||||||
|
|
||||||
|
|
||||||
// Collection of questions
|
|
||||||
public List<ResponseQuestionViewModel> Questions { get; set; } = new List<ResponseQuestionViewModel>();
|
public List<ResponseQuestionViewModel> Questions { get; set; } = new List<ResponseQuestionViewModel>();
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Web.ViewModel.QuestionnaireVM
|
||||||
|
{
|
||||||
|
public class ResponseQuestionnaireWithUsersViewModel
|
||||||
|
{
|
||||||
|
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
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string? UserName { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string? Email { get; set; }
|
||||||
|
public int ParticipantCount { get; set; }
|
||||||
|
public double QuestionsAnsweredPercentage { get; set; }
|
||||||
|
|
||||||
|
public List<ResponseQuestionViewModel> Questions { get; set; } = new List<ResponseQuestionViewModel>();
|
||||||
|
|
||||||
|
public List<ResponseUserViewModel> Users { get; set; }=new List<ResponseUserViewModel> { };
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Web/ViewModel/QuestionnaireVM/ResponseUserViewModel.cs
Normal file
8
Web/ViewModel/QuestionnaireVM/ResponseUserViewModel.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Web.ViewModel.QuestionnaireVM
|
||||||
|
{
|
||||||
|
public class ResponseUserViewModel
|
||||||
|
{
|
||||||
|
public string? UserName { get; set; }
|
||||||
|
public string? Email { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
153
Web/Views/QuestionnaireResponse/SubmittedSurvey.cshtml
Normal file
153
Web/Views/QuestionnaireResponse/SubmittedSurvey.cshtml
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "SubmittedSurvey";
|
||||||
|
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">
|
||||||
|
<h5 class="text-white">Thank You, <span class="text-info font-weight-bolder">@ViewBag.submitedEmail</span>, you have already taken the survey.</h5>
|
||||||
|
|
||||||
|
</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>
|
||||||
|
|
||||||
|
|
@ -135,12 +135,10 @@
|
||||||
|
|
||||||
<div class="card" id="Errocard">
|
<div class="card" id="Errocard">
|
||||||
<div class="col-lg-7" id="boxError">
|
<div class="col-lg-7" id="boxError">
|
||||||
<h4 class="text-white">Hey <span class="text-info font-weight-bold">@ViewBag.UserName</span></h4>
|
<h4 class="text-white">Hello <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>
|
|
||||||
|
|
||||||
|
<h6 class="text-white">Thank you for taking the time to submit the survey!</h6>
|
||||||
|
<p class="text-white">Your insights are invaluable to us and help us improve our services.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-5" id="boxError">
|
<div class="col-lg-5" id="boxError">
|
||||||
<script src="https://unpkg.com/@@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
|
<script src="https://unpkg.com/@@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
"ConfirmEmailPath": "Subscription/Confirmation",
|
"ConfirmEmailPath": "Subscription/Confirmation",
|
||||||
"unsubscribePath": "Subscription/UnsubscribeConfirmation",
|
"unsubscribePath": "Subscription/UnsubscribeConfirmation",
|
||||||
"Questionnaire": "QuestionnaireResponse/DisplayQuestionnaire",
|
"Questionnaire": "QuestionnaireResponse/DisplayQuestionnaire",
|
||||||
"EncryptionKey": ""
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"MailJet": {
|
"MailJet": {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue