real-time notification completed

This commit is contained in:
Qais Yousuf 2024-06-06 15:24:04 +02:00
parent aacc23d5ef
commit 196c6887a8
16 changed files with 846 additions and 218 deletions

View file

@ -17,6 +17,8 @@ namespace Model
public string? UserName { get; set; } // To store the user's name public string? UserName { get; set; } // To store the user's name
public string? UserEmail { get; set; } // To store the user's email public string? UserEmail { get; set; } // To store the user's email
public DateTime SubmissionDate { get; set; } public DateTime SubmissionDate { get; set; }
public List<ResponseDetail> ResponseDetails { get; set; } = new List<ResponseDetail>(); public List<ResponseDetail> ResponseDetails { get; set; } = new List<ResponseDetail>();
} }
} }

View file

@ -0,0 +1,14 @@

using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Services.Implemnetation
{
public class NotificationHub:Hub
{
}
}

View file

@ -8,6 +8,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="MailJet.Api" Version="3.0.0" /> <PackageReference Include="MailJet.Api" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.4" />
<ProjectReference Include="..\Data\Data.csproj" /> <ProjectReference Include="..\Data\Data.csproj" />
<ProjectReference Include="..\Model\Model.csproj" /> <ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup> </ItemGroup>

View file

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Model; using Model;
using Services.Interaces; using Services.Interaces;
using System.Security.Claims;
using Web.ViewModel.DashboardVM; using Web.ViewModel.DashboardVM;
namespace Web.Areas.Admin.Controllers namespace Web.Areas.Admin.Controllers
@ -14,11 +15,13 @@ namespace Web.Areas.Admin.Controllers
{ {
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IDashboardRepository _dashboard; private readonly IDashboardRepository _dashboard;
private readonly UserManager<ApplicationUser> _userManager;
public AdminController(SignInManager<ApplicationUser> signInManager,IDashboardRepository dashboard) public AdminController(SignInManager<ApplicationUser> signInManager,IDashboardRepository dashboard, UserManager<ApplicationUser> userManager)
{ {
_signInManager = signInManager; _signInManager = signInManager;
_dashboard = dashboard; _dashboard = dashboard;
_userManager = userManager;
} }
public async Task<IActionResult> Index() public async Task<IActionResult> Index()
{ {
@ -30,12 +33,56 @@ namespace Web.Areas.Admin.Controllers
{ {
ModelCounts = modelCounts, ModelCounts = modelCounts,
BannerSelections = bannerSelections, BannerSelections = bannerSelections,
FooterSelections = footerSelections FooterSelections = footerSelections,
PerformanceData = new List<PerformanceDataViewModel>(),
VisitorData = new List<VisitorDataViewModel>() // Initialize the new property
}; };
if (User.Identity.IsAuthenticated)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await _userManager.FindByIdAsync(userId);
if (user != null)
{
viewModel.FirstName = user.FirstName;
viewModel.LastName = user.LastName;
}
}
else
{
viewModel.FirstName = "Guest";
viewModel.LastName = string.Empty;
}
return View(viewModel); return View(viewModel);
} }
[HttpGet]
public JsonResult GetVisitorData()
{
var visitorData = new List<VisitorDataViewModel>
{
new VisitorDataViewModel { Time = DateTime.Now.ToString("HH:mm:ss"), VisitorCount = new Random().Next(0, 500) },
new VisitorDataViewModel { Time = DateTime.Now.AddSeconds(-5).ToString("HH:mm:ss"), VisitorCount = new Random().Next(0, 500) },
new VisitorDataViewModel { Time = DateTime.Now.AddSeconds(-10).ToString("HH:mm:ss"), VisitorCount = new Random().Next(0, 500) }
};
return Json(visitorData);
}
[HttpGet]
public JsonResult GetPerformanceData()
{
var performanceData = new List<PerformanceDataViewModel>
{
new PerformanceDataViewModel { Time = DateTime.Now.ToString("HH:mm:ss"), CPUUsage = new Random().Next(0, 100), MemoryUsage = new Random().Next(0, 100) },
new PerformanceDataViewModel { Time = DateTime.Now.AddSeconds(-5).ToString("HH:mm:ss"), CPUUsage = new Random().Next(0, 100), MemoryUsage = new Random().Next(0, 100) },
new PerformanceDataViewModel { Time = DateTime.Now.AddSeconds(-10).ToString("HH:mm:ss"), CPUUsage = new Random().Next(0, 100), MemoryUsage = new Random().Next(0, 100) }
};
return Json(performanceData);
}
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
public async Task<IActionResult> Logout() public async Task<IActionResult> Logout()

View file

@ -289,24 +289,259 @@ namespace Web.Areas.Admin.Controllers
} }
//private IActionResult GenerateExcelReport(List<Response> responses)
public async Task<IActionResult> GenerateQuestionnairePdfReport(int questionnaireId)
{
var response = await _context.Responses
.Include(r => r.Questionnaire)
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.Question)
.ThenInclude(q => q.Answers)
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.ResponseAnswers)
.FirstOrDefaultAsync(r => r.QuestionnaireId == questionnaireId);
if (response == null)
{
return NotFound();
}
return GeneratePdfReportForQuestionnaire(response);
}
private IActionResult GeneratePdfReportForQuestionnaire(Response response)
{
var userName = response.UserName;
var userEmail = response.UserEmail;
var stream = new MemoryStream();
var document = new Document(PageSize.A4, 50, 50, 25, 25);
var writer = PdfWriter.GetInstance(document, stream);
writer.CloseStream = false; // Prevent the stream from being closed when the document is closed
document.Open();
// Add a title
var titleFont = FontFactory.GetFont(FontFactory.HELVETICA_BOLD, 18, BaseColor.BLACK);
var title = new Paragraph($"Report for {response.Questionnaire.Title}", titleFont)
{
Alignment = Element.ALIGN_CENTER,
SpacingAfter = 20
};
document.Add(title);
// Add a logo
var logoPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images", "logo.png");
if (System.IO.File.Exists(logoPath))
{
var logo = Image.GetInstance(logoPath);
logo.ScaleToFit(100f, 100f);
logo.Alignment = Image.ALIGN_CENTER;
document.Add(logo);
}
// Add a table
var headerFont = FontFactory.GetFont(FontFactory.HELVETICA_BOLD, 14, BaseColor.WHITE);
var cellFont = FontFactory.GetFont(FontFactory.HELVETICA, 12, BaseColor.BLACK);
var table = new PdfPTable(2)
{
WidthPercentage = 100,
SpacingBefore = 20,
SpacingAfter = 20
};
table.SetWidths(new float[] { 1, 3 });
var cellForResponse = new PdfPCell(new Phrase($"{response.UserName} ({response.UserEmail})", headerFont))
{
Colspan = 2,
BackgroundColor = new BaseColor(0, 150, 0),
HorizontalAlignment = Element.ALIGN_CENTER,
Padding = 10
};
table.AddCell(cellForResponse);
var cell = new PdfPCell(new Phrase($"Survey: {response.Questionnaire.Title}", headerFont))
{
Colspan = 2,
BackgroundColor = new BaseColor(0, 150, 0),
HorizontalAlignment = Element.ALIGN_CENTER,
Padding = 10
};
table.AddCell(cell);
table.AddCell(new PdfPCell(new Phrase("Submitted on:", cellFont)) { Padding = 5 });
table.AddCell(new PdfPCell(new Phrase(response.SubmissionDate.ToString(), cellFont)) { Padding = 5 });
foreach (var detail in response.ResponseDetails)
{
table.AddCell(new PdfPCell(new Phrase("Question:", cellFont)) { Padding = 5 });
table.AddCell(new PdfPCell(new Phrase(detail.Question.Text, cellFont)) { Padding = 5 });
if (detail.QuestionType == QuestionType.Text || detail.QuestionType == QuestionType.Slider || detail.QuestionType == QuestionType.Open_ended)
{
table.AddCell(new PdfPCell(new Phrase("Answer:", cellFont)) { Padding = 5 });
table.AddCell(new PdfPCell(new Phrase(detail.TextResponse, cellFont)) { Padding = 5 });
}
else
{
table.AddCell(new PdfPCell(new Phrase("Answers:", cellFont)) { Padding = 5 });
var answers = string.Join(", ", detail.ResponseAnswers.Select(a => detail.Question.Answers.FirstOrDefault(ans => ans.Id == a.AnswerId)?.Text));
table.AddCell(new PdfPCell(new Phrase(answers, cellFont)) { Padding = 5 });
}
}
document.Add(table);
document.Close();
writer.Close();
stream.Position = 0;
return File(stream, "application/pdf", $"{response.Questionnaire.Title}_{userEmail}.pdf");
}
public async Task<IActionResult> GenerateQuestionnaireExcelReport(int questionnaireId)
{
var response = await _context.Responses
.Include(r => r.Questionnaire)
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.Question)
.ThenInclude(q => q.Answers)
.Include(r => r.ResponseDetails)
.ThenInclude(rd => rd.ResponseAnswers)
.FirstOrDefaultAsync(r => r.QuestionnaireId == questionnaireId);
if (response == null)
{
return NotFound();
}
return GenerateExcelReportForQuestionnaire(response);
}
private IActionResult GenerateExcelReportForQuestionnaire(Response response)
{
var userName = response.UserName;
var userEmail = response.UserEmail;
using (var package = new ExcelPackage())
{
var worksheet = package.Workbook.Worksheets.Add("Report");
// Add a logo
var logoPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images", "logo.png");
if (System.IO.File.Exists(logoPath))
{
var logo = new FileInfo(logoPath);
var picture = worksheet.Drawings.AddPicture("Logo", logo);
picture.SetPosition(0, 0, 2, 0);
picture.SetSize(300, 60); // Adjust the size as needed
}
// Add user details
worksheet.Cells[5, 1].Value = $"{userName} ({userEmail})";
worksheet.Cells[5, 1, 5, 4].Merge = true;
worksheet.Cells[5, 1, 5, 4].Style.Font.Size = 15;
worksheet.Cells[5, 1, 5, 4].Style.Font.Bold =true;
worksheet.Cells[5, 1, 5, 4].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
// Add a title
worksheet.Cells[6, 1].Value = $"Report for {response.Questionnaire.Title}";
worksheet.Cells[6, 1, 6, 4].Merge = true;
worksheet.Cells[6, 1, 6, 4].Style.Font.Size = 18;
worksheet.Cells[6, 1, 6, 4].Style.Font.Bold = true;
worksheet.Cells[6, 1, 6, 4].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
// Add headers
worksheet.Cells[7, 1].Value = "Survey";
worksheet.Cells[7, 2].Value = "Submitted on";
worksheet.Cells[7, 3].Value = "Question";
worksheet.Cells[7, 4].Value = "Response";
using (var range = worksheet.Cells[7, 1, 7, 4])
{
range.Style.Font.Bold = true;
range.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
range.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray);
range.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
}
// Add data
var row = 8;
worksheet.Cells[row, 1].Value = response.Questionnaire.Title;
worksheet.Cells[row, 2].Value = response.SubmissionDate.ToString();
row++;
foreach (var detail in response.ResponseDetails)
{
worksheet.Cells[row, 3].Value = detail.Question.Text;
if (detail.QuestionType == QuestionType.Text || detail.QuestionType == QuestionType.Slider || detail.QuestionType == QuestionType.Open_ended)
{
worksheet.Cells[row, 4].Value = detail.TextResponse;
}
else
{
var answers = string.Join(", ", detail.ResponseAnswers.Select(a => detail.Question.Answers.FirstOrDefault(ans => ans.Id == a.AnswerId)?.Text));
worksheet.Cells[row, 4].Value = answers;
}
row++;
}
worksheet.Cells.AutoFitColumns();
var stream = new MemoryStream();
package.SaveAs(stream);
stream.Position = 0;
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", $"{response.Questionnaire.Title}_{userEmail}.xlsx");
}
}
//private IActionResult GenerateExcelReportForQuestionnaire(Response response)
//{ //{
// var userName = responses.First().UserName; // var userName = response.UserName;
// var userEmail = responses.First().UserEmail; // var userEmail = response.UserEmail;
// using (var package = new ExcelPackage()) // using (var package = new ExcelPackage())
// { // {
// var worksheet = package.Workbook.Worksheets.Add("Report"); // var worksheet = package.Workbook.Worksheets.Add("Report");
// worksheet.Cells[1, 1].Value = $"Report for {userName} ({userEmail})"; // // Add a logo
// worksheet.Cells[2, 1].Value = "Survey"; // var logoPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "images", "logo.png");
// worksheet.Cells[2, 2].Value = "Submitted on"; // if (System.IO.File.Exists(logoPath))
// worksheet.Cells[2, 3].Value = "Question";
// worksheet.Cells[2, 4].Value = "Response";
// var row = 3;
// foreach (var response in responses)
// { // {
// var logo = new FileInfo(logoPath);
// var picture = worksheet.Drawings.AddPicture("Logo", logo);
// picture.SetPosition(0, 0, 0, 0);
// picture.SetSize(300, 70); // Adjust the size as needed
// }
// // Add a title
// worksheet.Cells[6, 1].Value = $"Report for {response.Questionnaire.Title}";
// worksheet.Cells[6, 1, 6, 4].Merge = true;
// worksheet.Cells[6, 1, 6, 4].Style.Font.Size = 18;
// worksheet.Cells[6, 1, 6, 4].Style.Font.Bold = true;
// worksheet.Cells[6, 1, 6, 4].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
// // Add headers
// worksheet.Cells[7, 1].Value = "Survey";
// worksheet.Cells[7, 2].Value = "Submitted on";
// worksheet.Cells[7, 3].Value = "Question";
// worksheet.Cells[7, 4].Value = "Response";
// using (var range = worksheet.Cells[7, 1, 7, 4])
// {
// range.Style.Font.Bold = true;
// range.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
// range.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray);
// range.Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
// }
// // Add data
// var row = 8;
// worksheet.Cells[row, 1].Value = response.Questionnaire.Title; // worksheet.Cells[row, 1].Value = response.Questionnaire.Title;
// worksheet.Cells[row, 2].Value = response.SubmissionDate.ToString(); // worksheet.Cells[row, 2].Value = response.SubmissionDate.ToString();
// row++; // row++;
@ -326,15 +561,19 @@ namespace Web.Areas.Admin.Controllers
// } // }
// row++; // row++;
// } // }
// row++;
// } // worksheet.Cells.AutoFitColumns();
// var stream = new MemoryStream(); // var stream = new MemoryStream();
// package.SaveAs(stream); // package.SaveAs(stream);
// stream.Position = 0; // stream.Position = 0;
// return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", $"{userName}_report.xlsx"); // return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", $"{response.Questionnaire.Title}_{userEmail}.xlsx");
// } // }
//} //}
} }
} }

View file

@ -1,11 +1,64 @@
@model DashboardViewModel @model DashboardViewModel
@{
ViewData["Title"] = "Admin";
}
<style>
#BackgroundColor{
background-color:white;
}
</style>
<div class="container-fluid">
<div class="bd-callout bd-callout-primary shadow p-3" id="BackgroundColor">
<div class="row">
<div class="col-md-3 p-3">
<h4 class="display-5">Welcome</h4>
<h4 class="font-weight-bold text-success"><i class="bi bi-person-fill-check"></i> @Model.FirstName @Model.LastName</h4>
</div>
<div class="col-md-3">
<div id="modelCountChart" style="width: 100%; height: 150px;"></div>
</div>
<div class="col-md-3">
<div id="bannerSelectionChart" style="width: 100%; height: 150px;"></div>
</div>
<div class="col-md-3">
<div id="footerSelectionChart" style="width: 100%; height: 150px;"></div>
</div>
</div>
</div>
<div class="bd-callout bd-callout-primary shadow p-3" id="BackgroundColor">
<div class="row">
<div class="col-md-6">
<div id="performance_chart" style="width: 100%; height: 500px;"></div>
</div>
<div class="col-md-6">
<div id="visitor_chart" style="width: 100%; height: 500px;"></div>
</div>
</div>
</div>
</div>
<h2>Admin Dashboard</h2>
<div id="modelCountChart" style="width: 100%; height: 500px;"></div>
<div id="bannerSelectionChart" style="width: 100%; height: 500px;"></div>
<div id="footerSelectionChart" style="width: 100%; height: 500px;"></div>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script> <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript"> <script type="text/javascript">
google.charts.load('current', { 'packages': ['corechart'] }); google.charts.load('current', { 'packages': ['corechart'] });
google.charts.setOnLoadCallback(drawModelCountChart); google.charts.setOnLoadCallback(drawModelCountChart);
@ -22,7 +75,7 @@
]); ]);
var options = { var options = {
title: 'Model Counts', title: 'Model Usage of page',
}; };
@ -66,3 +119,96 @@
chart.draw(data, options); chart.draw(data, options);
} }
</script> </script>
<script type="text/javascript">
google.charts.load('current', { 'packages': ['corechart'] });
google.charts.setOnLoadCallback(drawCharts);
var performanceChart;
var performanceData;
var performanceOptions = {
title: 'Application Performance',
hAxis: { title: 'Time' },
vAxes: {
0: { title: 'CPU Usage (%)' },
1: { title: 'Memory Usage (MB)' }
},
series: {
0: { targetAxisIndex: 0 }, // First series (CPU) on first y-axis
1: { targetAxisIndex: 1 } // Second series (Memory) on second y-axis
},
legend: { position: 'bottom' },
colors: ['#a52714', '#097138'] // Example colors for CPU and Memory
};
var visitorChart;
var visitorData;
var visitorOptions = {
title: 'Website Visitors',
hAxis: { title: 'Time' },
vAxis: { title: 'Visitors' },
legend: { position: 'bottom' },
colors: ['#1c91c0']
};
function drawCharts() {
// Draw performance chart
performanceData = new google.visualization.DataTable();
performanceData.addColumn('string', 'Time');
performanceData.addColumn('number', 'CPU Usage');
performanceData.addColumn('number', 'Memory Usage');
performanceChart = new google.visualization.LineChart(document.getElementById('performance_chart'));
fetchPerformanceData();
// Draw visitor chart
visitorData = new google.visualization.DataTable();
visitorData.addColumn('string', 'Time');
visitorData.addColumn('number', 'Visitors');
visitorChart = new google.visualization.LineChart(document.getElementById('visitor_chart'));
fetchVisitorData();
}
function fetchPerformanceData() {
fetch('@Url.Action("GetPerformanceData", "admin")')
.then(response => response.json())
.then(performanceDataArray => {
updatePerformanceChart(performanceDataArray);
})
.catch(error => console.error('Error fetching performance data:', error));
}
function fetchVisitorData() {
fetch('@Url.Action("GetVisitorData", "admin")')
.then(response => response.json())
.then(visitorDataArray => {
updateVisitorChart(visitorDataArray);
})
.catch(error => console.error('Error fetching visitor data:', error));
}
function updatePerformanceChart(performanceDataArray) {
performanceData.removeRows(0, performanceData.getNumberOfRows());
performanceDataArray.forEach(point => {
performanceData.addRow([point.time, point.cpuUsage, point.memoryUsage]);
});
performanceChart.draw(performanceData, performanceOptions);
}
function updateVisitorChart(visitorDataArray) {
visitorData.removeRows(0, visitorData.getNumberOfRows());
visitorDataArray.forEach(point => {
visitorData.addRow([point.time, point.visitorCount]);
});
visitorChart.draw(visitorData, visitorOptions);
}
// Fetch data every 5 seconds
setInterval(fetchPerformanceData, 5000);
setInterval(fetchVisitorData, 5000);
</script>

View file

@ -6,10 +6,13 @@
<div class="container-fluid d-flex justify-content-center"> <div class="container-fluid p-4">
<div>
<a class="btn btn-primary" asp-action="Index">Back to List</a>
</div>
<div class="card "> <div class="card">
<div class="card-header"> <div class="card-header">
Details Details
</div> </div>
@ -20,7 +23,9 @@
<h4>Questionnaire details</h4> <h4>Questionnaire details</h4>
<br /> <br />
<div>
<span class="badge badge-warning shadow text-white m-1 p-2">ID</span>
</div>
<div> <div>
<span class="badge badge-primary shadow m-1 p-2">Questionnaire</span> <span class="badge badge-primary shadow m-1 p-2">Questionnaire</span>
</div> </div>
@ -32,45 +37,26 @@
</div> </div>
</div> </div>
<table class="table table-bordered table-responsive table-hover "> <table class="table table-responsive w-100 d-block d-md-table table-bordered table-hover ">
<thead> <thead>
<tr > <tr >
<th scope="col" class="text-primary h5">Id</th> <th scope="col" class="text-warning h5">ID</th>
<th scope="col" class="text-primary h5">Questionnaire</th> <th scope="col" class="text-primary h5">Questionnaire</th>
<th scope="col" class="text-primary h5">Description</th>
<th scope="col" class="text-success h5 ">Questions</th> <th scope="col" class="text-success h5 ">Questions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<th scope="row"><span class="badge p-2 m-1 bg-primary shadow-sm rounded">@Model.Id</span></th> <th scope="row"><span class="badge p-2 m-1 bg-warning shadow-sm rounded">@Model.Id</span></th>
<th scope="row"> <th scope="row">
<div class="title-container">
<div class="title-text">
<span class="item-title ">@Html.Raw(Model.Title.Length >= 30 ? Model.Title.Substring(0, 30) : Model.Title)</span> <span class="badge p-2 m-1 bg-primary shadow-sm rounded">@Model.Title</span>
<span class="more-title " style="display:none;">@(Model.Title.Length > 30 ? Model.Title.Substring(20) : "")</span>
<a href="#" id="ReadMore" class="read-more-title-btn ">Read More</a>
</div>
</div>
</th> </th>
<th scope="row">
<div class="description-container">
<div class="description-text">
<span class="item-description">@Html.Raw(Model.Description.Length >= 30 ? Model.Description.Substring(0, 30) : Model.Title)</span>
<span class="more-text " style="display:none;">@(Model.Description.Length > 30 ? Model.Description.Substring(30) : "")</span>
<a href="#" id="ReadMore" class="read-more-btn ">Read More</a>
</div>
</div>
</th>
<td class="h6"> <td class="h6">
<table> <table class="table table-responsive w-100 d-block d-md-table table-bordered table-hover ">
<tr > <tr >
<th class="text-success">Id</th> <th class="text-success">Id</th>
<th class="text-success">Question</th> <th class="text-success">Question</th>
@ -83,18 +69,7 @@
<tr> <tr>
<td> <span class="badge p-2 m-1 bg-success ">@question.Id</span></td> <td> <span class="badge p-2 m-1 bg-success ">@question.Id</span></td>
<td> <td>
<div class="question-container"> <span class="badge p-2 m-1 bg-success ">@question.Text</span>
<div class="question-text">
<span class="badge badge-success p-2">
<span class="item-question">@Html.Raw(question.Text.Length >= 30 ? question.Text.Substring(0, 30) : question.Text)</span>
<span class="more-question" style="display:none;">@(question.Text.Length > 30 ? question.Text.Substring(30) : "")</span>
<a href="#" id="ReadMore" class="read-more-btn-question mt-1 text-white ">Read More</a>
</span>
</div>
</div>
</td> </td>
<td> <td>
<span class="badge p-2 m-1 bg-success ">@question.Type</span> <span class="badge p-2 m-1 bg-success ">@question.Type</span>
@ -132,10 +107,10 @@
</tbody> </tbody>
</table> </table>
<footer> <div>
<a class="btn btn-primary" asp-action="Index">Back to List</a> <a class="btn btn-primary" asp-action="Index">Back to List</a>
</footer> </div>
</div> </div>
</div> </div>
@ -144,65 +119,3 @@
</div> </div>
@section Scripts {
<script>
$(document).ready(function () {
$('.read-more-btn').click(function () {
var $descriptionText = $(this).closest('.description-container').find('.description-text');
var $moreText = $descriptionText.find('.more-text');
var $toggleMore = $descriptionText.find('.toggle-more');
if ($moreText.is(':visible')) {
$moreText.slideUp();
$toggleMore.fadeIn();
} else {
$moreText.slideDown();
$toggleMore.fadeOut();
}
$(this).text(function (i, text) {
return text === "Read More" ? "Read Less" : "Read More";
});
});
$('.read-more-title-btn').click(function () {
var $titleText = $(this).closest('.title-container').find('.title-text');
var $moreTitle = $titleText.find('.more-title');
var $toggleMore = $titleText.find('.toggle-more');
if ($moreTitle.is(':visible')) {
$moreTitle.slideUp();
$toggleMore.fadeIn();
} else {
$moreTitle.slideDown();
$toggleMore.fadeOut();
}
$(this).text(function (i, text) {
return text === "Read More" ? "Read Less" : "Read More";
});
});
$('.read-more-btn-question').click(function () {
var $titleText = $(this).closest('.question-container').find('.question-text');
var $moreTitle = $titleText.find('.more-question');
var $toggleMore = $titleText.find('.toggle-more');
if ($moreTitle.is(':visible')) {
$moreTitle.slideUp();
$toggleMore.fadeIn();
} else {
$moreTitle.slideDown();
$toggleMore.fadeOut();
}
$(this).text(function (i, text) {
return text === "Read More" ? "Read Less" : "Read More";
});
});
});
</script>
}

View file

@ -16,17 +16,16 @@
<p> <p>
<a asp-action="Create" class="btn btn-primary"><span><i class="bi bi-plus-square-fill"></i></span> Create New</a> <a asp-action="Create" class="btn btn-primary"><span><i class="bi bi-plus-square-fill"></i></span> Create New</a>
</p> </p>
<table class="table table-responsive table-light table-hover "> <table class="table table-responsive w-100 d-block d-md-table table-bordered table-hover ">
<thead class="w-auto"> <thead class="w-auto">
<tr> <tr>
<th scope="col">Id</th> <th scope="col">Id</th>
<th scope="col">Title</th> <th scope="col">Title</th>
<th scope="col">Description</th>
<th scope="col">Total Questions</th> <th scope="col">Total Questions</th>
<th scope="col"> <span class="badge badge-primary">Questions</span> | <span class="badge badge-info">Type</span> | <span class="badge badge-success">Answers </span></th> <th scope="col"> <span class="badge badge-primary">Questions</span> | <span class="badge badge-info">Type</span> | <span class="badge badge-success">Answers </span></th>
<th scope="col" class="d-flex justify-content-end">Action</th> <th scope="col">Action</th>
</tr> </tr>
</thead> </thead>
<tbody class="w-auto"> <tbody class="w-auto">
@ -35,28 +34,11 @@
<tr class="table-secondary"> <tr class="table-secondary">
<td>@item.Id</td> <td>@item.Id</td>
@* <td> @item.Title</td> *@
<td> <td>
<div class="title-container"> @item.Title
<div class="title-text">
<span class="item-title">@Html.Raw(item.Title.Length >= 30 ? item.Title.Substring(0, 30) : item.Title)</span>
<span class="more-title" style="display:none;">@(item.Title.Length > 30 ? item.Title.Substring(30) : "")</span>
<a href="#" id="ReadMore" class="read-more-title-btn">Read More</a>
</div>
</div>
</td> </td>
@* <td>@Html.Raw(item.Description)</td> *@
<td>
<!-- Display only a portion of the description initially -->
<div class="description-container">
<div class="description-text">
<span class="item-description">@Html.Raw(item.Description.Length >= 30 ? item.Description.Substring(0, 30) : item.Title)</span>
<span class="more-text" style="display:none;">@(item.Description.Length > 30 ? item.Description.Substring(30) : "")</span>
<a href="#" id="ReadMore" class=" read-more-btn">Read More</a>
</div>
</div>
</td>
<td> <td>
@* <button type="button" class="btn btn-primary btn-sm"> @* <button type="button" class="btn btn-primary btn-sm">
@ -106,23 +88,7 @@
@section Scripts{ @section Scripts{
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$('.read-more-btn').click(function () {
var $descriptionText = $(this).closest('.description-container').find('.description-text');
var $moreText = $descriptionText.find('.more-text');
var $toggleMore = $descriptionText.find('.toggle-more');
if ($moreText.is(':visible')) {
$moreText.slideUp();
$toggleMore.fadeIn();
} else {
$moreText.slideDown();
$toggleMore.fadeOut();
}
$(this).text(function (i, text) {
return text === "Read More" ? "Read Less" : "Read More";
});
});
$('.read-more-title-btn').click(function () { $('.read-more-title-btn').click(function () {
var $titleText = $(this).closest('.title-container').find('.title-text'); var $titleText = $(this).closest('.title-container').find('.title-text');
var $moreTitle = $titleText.find('.more-title'); var $moreTitle = $titleText.find('.more-title');

View file

@ -64,7 +64,6 @@
<nav class="navbar navbar-expand-lg navbar-light bg-brimary"> <nav class="navbar navbar-expand-lg navbar-light bg-brimary">
<div class="container-fluid"> <div class="container-fluid">
<button type="button" id="sidebarCollapse" class="btn btn-primary"> <button type="button" id="sidebarCollapse" class="btn btn-primary">
<i class="bi bi-list"></i> <i class="bi bi-list"></i>
<span class="sr-only">Toggle Menu</span> <span class="sr-only">Toggle Menu</span>
@ -75,24 +74,41 @@
<span class="sr-only"></span> <span class="sr-only"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<div class=" collapse navbar-collapse" id="navbarSupportedContent">
<ul class="nav navbar-nav ml-auto"> <ul class="nav navbar-nav ml-auto">
<span class="dropdown mr-2">
<button class="btn btn-info btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Notifications <span class="badge badge-danger" id="notificationCount">0</span>
</a>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="#">
<h6 class="dropdown-header">New Notifications</h6>
<div id="notifications">
<div class="dropdown mr-2"> </div>
</a>
</li>
</ul>
</span>
<span class="dropdown mr-2">
<button class="btn btn-primary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button class="btn btn-primary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Account Account
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li> <a class="dropdown-item" asp-controller="Roles" asp-action="index"> Roles</a></li> <li><a class="dropdown-item" asp-controller="Roles" asp-action="index">Roles</a></li>
<li> <a class="dropdown-item" asp-controller="Users" asp-action="index"> Users</a></li> <li><a class="dropdown-item" asp-controller="Users" asp-action="index">Users</a></li>
</ul> </ul>
</div> </span>
<li class="nav-item"> <li class="nav-item">
<form asp-area="Admin" asp-controller="Admin" asp-action="Logout" method="post"> <form asp-area="Admin" asp-controller="Admin" asp-action="Logout" method="post">
<button type="submit" class="btn btn-danger btn-sm"> <span class="bi bi-box-arrow-left"></span> Logout</button> <button type="submit" class="btn btn-danger btn-sm"><span class="bi bi-box-arrow-left"></span> Logout</button>
</form> </form>
</li> </li>
</ul> </ul>
@ -115,6 +131,40 @@
<script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script> <script src="~/js/site.js" asp-append-version="true"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/5.0.11/signalr.min.js"></script>
<script type="text/javascript">
// Establish a connection to the SignalR hub
const connection = new signalR.HubConnectionBuilder()
.withUrl("/notificationHub")
.build();
// Function to add a notification to the list
function addNotification(userName, email) {
const notificationsList = document.getElementById("notifications");
const li = document.createElement("li");
li.className = "list-group-item";
li.textContent = `New submission from ${userName}`;
notificationsList.appendChild(li);
// Update the notification count
const notificationCount = document.getElementById("notificationCount");
notificationCount.textContent = parseInt(notificationCount.textContent) + 1;
}
// Receive notification from the server
connection.on("ReceiveNotification", function (userName, email) {
addNotification(userName, email);
});
// Start the connection
connection.start().catch(function (err) {
return console.error(err.toString());
});
</script>
@await RenderSectionAsync("Scripts", required: false) @await RenderSectionAsync("Scripts", required: false)
</body> </body>
</html> </html>

View file

@ -1,4 +1,5 @@
@model UserResponsesViewModel @using Newtonsoft.Json
@model UserResponsesViewModel
@{ @{
ViewData["Title"] = "User Responses"; ViewData["Title"] = "User Responses";
@ -54,6 +55,17 @@
.step-header { .step-header {
margin-bottom: 10px; margin-bottom: 10px;
} }
#QuestionnairCard{
padding:2rem;
border: 0.5px solid #e7e7e7;
border-radius:3px;
-webkit-box-shadow: 0px 0px 14px -7px rgba(0,0,0,0.75);
-moz-box-shadow: 0px 0px 14px -7px rgba(0,0,0,0.75);
box-shadow: 0px 0px 14px -7px rgba(0,0,0,0.75);
}
</style> </style>
@ -63,21 +75,37 @@
<a asp-action="Index" class="btn btn-primary btn-sm">Back to list</a> <a asp-action="Index" class="btn btn-primary btn-sm">Back to list</a>
</p> </p>
<div class="card p-5 m-3 shadow"> <div class="card p-5 m-3">
<div class="bd-callout bd-callout-primary"> <div class="bd-callout bd-callout-primary shadow">
<div class="row p-4">
<div class="col-md-3 mt-5">
<h6 class="text-primary font-weight-bold"><i class="bi bi-person"></i> @Model.UserName (@Model.UserEmail)</h6> <h6 class="text-primary font-weight-bold"><i class="bi bi-person"></i> @Model.UserName (@Model.UserEmail)</h6>
<p class="text-info"><i class="bi bi-calculator"></i> Total responses: @Model.Responses.Count()</p> <p class="text-info font-weight-bold"><i class="bi bi-calculator"></i> Total responses: @Model.Responses.Count()</p>
<a asp-action="GenerateReport" asp-route-userEmail="@Model.UserEmail" asp-route-format="pdf" class="btn btn-info btn-sm"> <a asp-action="GenerateReport" asp-route-userEmail="@Model.UserEmail" asp-route-format="pdf" class="btn btn-info btn-sm">
<i class="bi bi-filetype-pdf"></i> Generate PDF Report <i class="bi bi-filetype-pdf"></i> PDF Reports
</a> </a>
<a asp-action="GenerateReport" asp-route-userEmail="@Model.UserEmail" asp-route-format="excel" class="btn btn-info btn-sm"> <a asp-action="GenerateReport" asp-route-userEmail="@Model.UserEmail" asp-route-format="excel" class="btn btn-info btn-sm">
<i class="bi bi-file-excel"></i> Generate Excel Report <i class="bi bi-file-excel"></i> Excel Reports
</a> </a>
</div> </div>
<div class="col-md-9">
<div id="user_survey_summary_chart_div" style=" width: 100%; height: 200px;"></div>
</div>
</div>
<hr class="border border-info border-1 opacity-50">
<div class="col-md-12 ">
<div id="chart_div" style="width: 100%; height: 400px;"></div>
</div>
</div>
<!-- Stepper --> <!-- Stepper -->
<div class="stepper-wrapper"> <div class="stepper-wrapper">
@foreach (var response in Model.Responses) @foreach (var response in Model.Responses)
@ -88,17 +116,39 @@
</div> </div>
<div class="step-content"> <div class="step-content">
<div class="card p-4"> <div id="QuestionnairCard">
<div class="row">
<div class="col-md-7 p-4">
<div class="step-header"> <div class="step-header">
<h6 class="text-primary font-weight-bold"><i class="bi bi-ui-checks"></i> @response.Questionnaire.Title</h6> <h6 class="text-primary font-weight-bold"><i class="bi bi-ui-checks"></i> @response.Questionnaire.Title</h6>
<h6 class="text-success"><i class="bi bi-calendar2-week"></i> Submitted on: @response.SubmissionDate</h6> <h6 class="text-success font-weight-bold"><i class="bi bi-calendar2-week"></i> Submitted on: @response.SubmissionDate</h6>
<p class="text-info"><i class="bi bi-question-square"></i> Total questions: @response.Questionnaire.Questions.Count()</p> <p class="text-info font-weight-bold"><i class="bi bi-question-square"></i> Total questions: @response.Questionnaire.Questions.Count()</p>
</div> </div>
<!-- Buttons to generate individual reports -->
<a asp-action="GenerateQuestionnairePdfReport" asp-route-questionnaireId="@response.Questionnaire.Id" class="btn btn-info btn-sm ">
<i class="bi bi-filetype-pdf"></i> PDF report
</a>
<a asp-action="GenerateQuestionnaireExcelReport" asp-route-questionnaireId="@response.Questionnaire.Id" class="btn btn-info btn-sm">
<i class="bi bi-file-excel"></i> Excel report
</a><br />
<!-- Buttons to generate individual reports -->
<!-- Collapsible button --> <!-- Collapsible button -->
<button class="btn btn-primary btn-sm mt-2" type="button" data-bs-toggle="collapse" data-bs-target="#collapseResponse-@response.Id" aria-expanded="false" aria-controls="collapseResponse-@response.Id"> <button class="btn btn-primary btn-sm mt-2" type="button" data-bs-toggle="collapse" data-bs-target="#collapseResponse-@response.Id" aria-expanded="false" aria-controls="collapseResponse-@response.Id">
View Responses View Responses
</button> </button>
</div>
<div class="col-md-5">
<div id="survey_questions_answers_summary_chart_div_@response.Questionnaire.Id" style="width: 100%; height: 200px;"></div>
</div>
</div>
<!-- Collapsible content --> <!-- Collapsible content -->
<div class="collapse mt-3" id="collapseResponse-@response.Id"> <div class="collapse mt-3" id="collapseResponse-@response.Id">
@ -170,7 +220,171 @@
@section Scripts { @section Scripts {
<!-- Include Bootstrap 5 JS for collapse functionality --> <!-- Include Bootstrap 5 JS for collapse functionality -->
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', { packages: ['corechart', 'line'] });
google.charts.setOnLoadCallback(drawChart);
function getDescriptiveValue(sliderValue) {
if (sliderValue >= 0 && sliderValue <= 10) {
return 1; // Normal
} else if (sliderValue > 10 && sliderValue <= 20) {
return 2; // Better
} else if (sliderValue > 20 && sliderValue <= 30) {
return 3; // Good
} else if (sliderValue > 30 && sliderValue <= 40) {
return 4; // Very Good
} else if (sliderValue > 40 && sliderValue <= 50) {
return 5; // Excellent
} else if (sliderValue > 50 && sliderValue <= 60) {
return 6; // Outstanding
} else if (sliderValue > 60 && sliderValue <= 70) {
return 7; // Remarkable
} else if (sliderValue > 70 && sliderValue <= 80) {
return 8; // Exceptional
} else if (sliderValue > 80 && sliderValue <= 90) {
return 9; // Superior
} else if (sliderValue > 90 && sliderValue <= 100) {
return 10; // Perfect
} else {
return 0; // Unknown
}
}
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Survey and Submission Date');
data.addColumn('number', 'Status');
var responsesData = [
@foreach (var response in Model.Responses)
{
@foreach (var detail in response.ResponseDetails.Where(d => d.QuestionType == QuestionType.Slider))
{
@:['@response.Questionnaire.Title - @response.SubmissionDate.ToString("yyyy-MMMM-dd HH:mm")', getDescriptiveValue(@(detail.TextResponse != null ? int.Parse(detail.TextResponse) : 0))],
}
}
];
responsesData.forEach(function (row) {
data.addRow(row);
});
var options = {
title: 'User Progress',
hAxis: {
title: 'Questionnaire and Submission Date'
},
vAxis: {
title: 'Status',
ticks: [
{ v: 0, f: 'Unknown' },
{ v: 1, f: 'Normal' },
{ v: 2, f: 'Better' },
{ v: 3, f: 'Good' },
{ v: 4, f: 'Very Good' },
{ v: 5, f: 'Excellent' },
{ v: 6, f: 'Outstanding' },
{ v: 7, f: 'Remarkable' },
{ v: 8, f: 'Exceptional' },
{ v: 9, f: 'Superior' },
{ v: 10, f: 'Perfect' }
]
},
'titleTextStyle': { color: '#17a2b8', fontSize: 16, bold: true },
};
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
</script>
<script type="text/javascript">
google.charts.load('current', { packages: ['corechart', 'bar'] });
google.charts.setOnLoadCallback(drawUserSurveySummaryChart);
function drawUserSurveySummaryChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'User');
data.addColumn('number', 'Total Surveys');
data.addColumn('number', 'Total Questions');
data.addColumn('number', 'Total Answers');
var userSurveySummaryData = @Html.Raw(JsonConvert.SerializeObject(
Model.Responses
.GroupBy(response => response.UserEmail)
.Select(g => new
{
UserName = g.First().UserName,
TotalSurveys = g.Count(),
TotalQuestions = g.SelectMany(r => r.ResponseDetails).Count(),
TotalAnswers = g.SelectMany(r => r.ResponseDetails.SelectMany(rd => rd.ResponseAnswers)).Count()
})
.ToList()
));
userSurveySummaryData.forEach(function (row) {
data.addRow([row.UserName, row.TotalSurveys, row.TotalQuestions, row.TotalAnswers]);
});
var options = {
title: 'User Survey Summary',
hAxis: {
title: 'Count'
},
vAxis: {
title: 'User'
},
'titleTextStyle': { color: '#17a2b8', fontSize: 16, bold: true },
};
var chart = new google.visualization.BarChart(document.getElementById('user_survey_summary_chart_div'));
chart.draw(data, options);
}
</script>
<script type="text/javascript">
google.charts.load('current', { packages: ['corechart'] });
google.charts.setOnLoadCallback(drawSurveyQuestionsAnswersSummaryCharts);
function drawSurveyQuestionsAnswersSummaryCharts() {
var surveyQuestionsSummaryData = @Html.Raw(JsonConvert.SerializeObject(
Model.Responses
.GroupBy(response => response.Questionnaire.Title)
.Select(g => new
{
SurveyTitle = g.Key,
SurveyId = g.First().Questionnaire.Id,
TotalQuestions = g.SelectMany(r => r.ResponseDetails).Count(),
TotalAnswers = g.SelectMany(r => r.ResponseDetails.SelectMany(rd => rd.ResponseAnswers)).Count()
})
.ToList()
));
surveyQuestionsSummaryData.forEach(function (row) {
drawSingleSurveyChart(row.SurveyTitle, row.SurveyId, row.TotalQuestions, row.TotalAnswers);
});
}
function drawSingleSurveyChart(surveyTitle, surveyId, totalQuestions, totalAnswers) {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Category');
data.addColumn('number', 'Count');
data.addRow(['Questions', totalQuestions]);
data.addRow(['Answers', totalAnswers]);
var options = {
is3D: true,
titleTextStyle: { color: '#17a2b8', fontSize: 16, bold: true },
};
var chart = new google.visualization.PieChart(document.getElementById('survey_questions_answers_summary_chart_div_' + surveyId));
chart.draw(data, options);
}
</script>
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
var collapseElements = document.querySelectorAll('[data-bs-toggle="collapse"]'); var collapseElements = document.querySelectorAll('[data-bs-toggle="collapse"]');

View file

@ -1,10 +1,12 @@
using Data; using Data;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage; using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
using Model; using Model;
using Newtonsoft.Json; using Newtonsoft.Json;
using Services.EmailSend; using Services.EmailSend;
using Services.Implemnetation;
using Services.Interaces; using Services.Interaces;
using System.Globalization; using System.Globalization;
using System.Security.Cryptography; using System.Security.Cryptography;
@ -20,12 +22,14 @@ namespace Web.Controllers
private readonly IQuestionnaireRepository _questionnaireRepository; private readonly IQuestionnaireRepository _questionnaireRepository;
private readonly SurveyContext _context; private readonly SurveyContext _context;
private readonly IEmailServices _emailServices; private readonly IEmailServices _emailServices;
private readonly IHubContext<NotificationHub> _hubContext;
public QuestionnaireResponseController(IQuestionnaireRepository questionnaireRepository,SurveyContext context, IEmailServices emailServices) public QuestionnaireResponseController(IQuestionnaireRepository questionnaireRepository,SurveyContext context, IEmailServices emailServices, IHubContext<NotificationHub> hubContext)
{ {
_questionnaireRepository = questionnaireRepository; _questionnaireRepository = questionnaireRepository;
_context = context; _context = context;
_emailServices = emailServices; _emailServices = emailServices;
_hubContext = hubContext;
} }
public IActionResult Index() public IActionResult Index()
{ {
@ -110,13 +114,14 @@ namespace Web.Controllers
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);
var cetZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
var cetTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, cetZone);
var response = new Response var response = new Response
{ {
QuestionnaireId = questionnaire.Id, QuestionnaireId = questionnaire.Id,
UserName = questionnaire.UserName, UserName = questionnaire.UserName,
UserEmail = questionnaire.Email, UserEmail = questionnaire.Email,
SubmissionDate = DateTime.UtcNow, SubmissionDate = cetTime,
ResponseDetails = questionnaire.Questions.Select(q => new ResponseDetail ResponseDetails = questionnaire.Questions.Select(q => new ResponseDetail
{ {
QuestionId = q.Id, QuestionId = q.Id,
@ -199,6 +204,8 @@ namespace Web.Controllers
TempData["UserName"] = questionnaire.UserName; TempData["UserName"] = questionnaire.UserName;
_hubContext.Clients.All.SendAsync("ReceiveNotification", questionnaire.UserName, questionnaire.Email);
return RedirectToAction(nameof(ThankYou)); return RedirectToAction(nameof(ThankYou));
} }

View file

@ -41,6 +41,7 @@ builder.Services.MailStatConfiguration();
builder.Services.ConfigureDashboard(); builder.Services.ConfigureDashboard();
builder.Services.UserResponseConfiguration(); builder.Services.UserResponseConfiguration();
builder.Services.ConfigureOpenAI(config); builder.Services.ConfigureOpenAI(config);
builder.Services.AddSignalR();
@ -88,4 +89,6 @@ app.MapControllerRoute(
pattern:"{controller=Home}/{action=Index}/{id?}"); pattern:"{controller=Home}/{action=Index}/{id?}");
app.MapHub<NotificationHub>("/notificationHub");
app.Run(); app.Run();

View file

@ -5,5 +5,11 @@
public Dictionary<string, int>? ModelCounts { get; set; } public Dictionary<string, int>? ModelCounts { get; set; }
public Dictionary<string, int>? BannerSelections { get; set; } public Dictionary<string, int>? BannerSelections { get; set; }
public Dictionary<string, int>? FooterSelections { get; set; } public Dictionary<string, int>? FooterSelections { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public List<PerformanceDataViewModel>? PerformanceData { get; set; }
public List<VisitorDataViewModel>? VisitorData { get; set; } // New property
} }
} }

View file

@ -0,0 +1,9 @@
namespace Web.ViewModel.DashboardVM
{
public class PerformanceDataViewModel
{
public string Time { get; set; }
public int CPUUsage { get; set; }
public int MemoryUsage { get; set; }
}
}

View file

@ -0,0 +1,10 @@
namespace Web.ViewModel.DashboardVM
{
public class VisitorDataViewModel
{
public string? Time { get; set; }
public int VisitorCount { get; set; }
}
}

View file

@ -18,6 +18,7 @@
<PackageReference Include="MailJet.Api" Version="3.0.0" /> <PackageReference Include="MailJet.Api" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter" Version="8.0.2" /> <PackageReference Include="Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter" Version="8.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>