real-time notification completed
This commit is contained in:
parent
aacc23d5ef
commit
196c6887a8
16 changed files with 846 additions and 218 deletions
|
|
@ -17,6 +17,8 @@ namespace Model
|
|||
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>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
Services/Implemnetation/NotificationHub.cs
Normal file
14
Services/Implemnetation/NotificationHub.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MailJet.Api" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.4" />
|
||||
<ProjectReference Include="..\Data\Data.csproj" />
|
||||
<ProjectReference Include="..\Model\Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Identity;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Model;
|
||||
using Services.Interaces;
|
||||
using System.Security.Claims;
|
||||
using Web.ViewModel.DashboardVM;
|
||||
|
||||
namespace Web.Areas.Admin.Controllers
|
||||
|
|
@ -14,11 +15,13 @@ namespace Web.Areas.Admin.Controllers
|
|||
{
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
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;
|
||||
_dashboard = dashboard;
|
||||
_userManager = userManager;
|
||||
}
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
|
|
@ -30,12 +33,56 @@ namespace Web.Areas.Admin.Controllers
|
|||
{
|
||||
ModelCounts = modelCounts,
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
[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]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Logout()
|
||||
|
|
|
|||
|
|
@ -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 userEmail = responses.First().UserEmail;
|
||||
// var userName = response.UserName;
|
||||
// var userEmail = response.UserEmail;
|
||||
|
||||
// using (var package = new ExcelPackage())
|
||||
// {
|
||||
// var worksheet = package.Workbook.Worksheets.Add("Report");
|
||||
|
||||
// worksheet.Cells[1, 1].Value = $"Report for {userName} ({userEmail})";
|
||||
// worksheet.Cells[2, 1].Value = "Survey";
|
||||
// worksheet.Cells[2, 2].Value = "Submitted on";
|
||||
// worksheet.Cells[2, 3].Value = "Question";
|
||||
// worksheet.Cells[2, 4].Value = "Response";
|
||||
|
||||
// var row = 3;
|
||||
// foreach (var response in responses)
|
||||
// // 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, 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, 2].Value = response.SubmissionDate.ToString();
|
||||
// row++;
|
||||
|
|
@ -326,15 +561,19 @@ namespace Web.Areas.Admin.Controllers
|
|||
// }
|
||||
// row++;
|
||||
// }
|
||||
// row++;
|
||||
// }
|
||||
|
||||
// worksheet.Cells.AutoFitColumns();
|
||||
|
||||
// var stream = new MemoryStream();
|
||||
// package.SaveAs(stream);
|
||||
// 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");
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,64 @@
|
|||
@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">
|
||||
google.charts.load('current', { 'packages': ['corechart'] });
|
||||
google.charts.setOnLoadCallback(drawModelCountChart);
|
||||
|
|
@ -22,7 +75,7 @@
|
|||
]);
|
||||
|
||||
var options = {
|
||||
title: 'Model Counts',
|
||||
title: 'Model Usage of page',
|
||||
|
||||
};
|
||||
|
||||
|
|
@ -66,3 +119,96 @@
|
|||
chart.draw(data, options);
|
||||
}
|
||||
</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>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,11 @@
|
|||
|
||||
|
||||
|
||||
<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-header">
|
||||
|
|
@ -20,7 +23,9 @@
|
|||
|
||||
<h4>Questionnaire details</h4>
|
||||
<br />
|
||||
|
||||
<div>
|
||||
<span class="badge badge-warning shadow text-white m-1 p-2">ID</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="badge badge-primary shadow m-1 p-2">Questionnaire</span>
|
||||
</div>
|
||||
|
|
@ -32,45 +37,26 @@
|
|||
</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>
|
||||
<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">Description</th>
|
||||
<th scope="col" class="text-success h5 ">Questions</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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">
|
||||
<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="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>
|
||||
<span class="badge p-2 m-1 bg-primary shadow-sm rounded">@Model.Title</span>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<table>
|
||||
<table class="table table-responsive w-100 d-block d-md-table table-bordered table-hover ">
|
||||
<tr >
|
||||
<th class="text-success">Id</th>
|
||||
<th class="text-success">Question</th>
|
||||
|
|
@ -83,18 +69,7 @@
|
|||
<tr>
|
||||
<td> <span class="badge p-2 m-1 bg-success ">@question.Id</span></td>
|
||||
<td>
|
||||
<div class="question-container">
|
||||
<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>
|
||||
<span class="badge p-2 m-1 bg-success ">@question.Text</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge p-2 m-1 bg-success ">@question.Type</span>
|
||||
|
|
@ -132,10 +107,10 @@
|
|||
</tbody>
|
||||
|
||||
</table>
|
||||
<footer>
|
||||
<div>
|
||||
<a class="btn btn-primary" asp-action="Index">Back to List</a>
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -144,65 +119,3 @@
|
|||
</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>
|
||||
|
||||
}
|
||||
|
|
@ -16,17 +16,16 @@
|
|||
<p>
|
||||
<a asp-action="Create" class="btn btn-primary"><span><i class="bi bi-plus-square-fill"></i></span> Create New</a>
|
||||
</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">
|
||||
<tr>
|
||||
|
||||
<th scope="col">Id</th>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col">Description</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" class="d-flex justify-content-end">Action</th>
|
||||
<th scope="col">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="w-auto">
|
||||
|
|
@ -35,28 +34,11 @@
|
|||
<tr class="table-secondary">
|
||||
|
||||
<td>@item.Id</td>
|
||||
@* <td> @item.Title</td> *@
|
||||
|
||||
<td>
|
||||
<div class="title-container">
|
||||
<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>
|
||||
@item.Title
|
||||
</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>
|
||||
@* <button type="button" class="btn btn-primary btn-sm">
|
||||
|
|
@ -106,23 +88,7 @@
|
|||
@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');
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@
|
|||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-brimary">
|
||||
<div class="container-fluid">
|
||||
|
||||
<button type="button" id="sidebarCollapse" class="btn btn-primary">
|
||||
<i class="bi bi-list"></i>
|
||||
<span class="sr-only">Toggle Menu</span>
|
||||
|
|
@ -75,21 +74,38 @@
|
|||
<span class="sr-only"></span>
|
||||
</button>
|
||||
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<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">
|
||||
Account
|
||||
</button>
|
||||
<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="Users" asp-action="index">Users</a></li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</span>
|
||||
<li class="nav-item">
|
||||
<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>
|
||||
|
|
@ -115,6 +131,40 @@
|
|||
<script src="~/lib/jquery/dist/jquery.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 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)
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
@model UserResponsesViewModel
|
||||
@using Newtonsoft.Json
|
||||
@model UserResponsesViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "User Responses";
|
||||
|
|
@ -54,6 +55,17 @@
|
|||
.step-header {
|
||||
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>
|
||||
|
||||
|
||||
|
|
@ -63,21 +75,37 @@
|
|||
<a asp-action="Index" class="btn btn-primary btn-sm">Back to list</a>
|
||||
</p>
|
||||
|
||||
<div class="card p-5 m-3 shadow">
|
||||
<div class="bd-callout bd-callout-primary">
|
||||
<div class="card p-5 m-3">
|
||||
<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>
|
||||
<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">
|
||||
<i class="bi bi-filetype-pdf"></i> Generate PDF Report
|
||||
<i class="bi bi-filetype-pdf"></i> PDF Reports
|
||||
</a>
|
||||
<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>
|
||||
</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 -->
|
||||
<div class="stepper-wrapper">
|
||||
@foreach (var response in Model.Responses)
|
||||
|
|
@ -88,17 +116,39 @@
|
|||
</div>
|
||||
<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">
|
||||
<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>
|
||||
<p class="text-info"><i class="bi bi-question-square"></i> Total questions: @response.Questionnaire.Questions.Count()</p>
|
||||
<h6 class="text-success font-weight-bold"><i class="bi bi-calendar2-week"></i> Submitted on: @response.SubmissionDate</h6>
|
||||
<p class="text-info font-weight-bold"><i class="bi bi-question-square"></i> Total questions: @response.Questionnaire.Questions.Count()</p>
|
||||
</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 -->
|
||||
<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
|
||||
</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 -->
|
||||
<div class="collapse mt-3" id="collapseResponse-@response.Id">
|
||||
|
|
@ -170,7 +220,171 @@
|
|||
|
||||
@section Scripts {
|
||||
<!-- 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>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var collapseElements = document.querySelectorAll('[data-bs-toggle="collapse"]');
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
using Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages.Manage;
|
||||
using Model;
|
||||
using Newtonsoft.Json;
|
||||
using Services.EmailSend;
|
||||
using Services.Implemnetation;
|
||||
using Services.Interaces;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
|
|
@ -20,12 +22,14 @@ namespace Web.Controllers
|
|||
private readonly IQuestionnaireRepository _questionnaireRepository;
|
||||
private readonly SurveyContext _context;
|
||||
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;
|
||||
_context = context;
|
||||
_emailServices = emailServices;
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
public IActionResult Index()
|
||||
{
|
||||
|
|
@ -110,13 +114,14 @@ namespace Web.Controllers
|
|||
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
|
||||
{
|
||||
QuestionnaireId = questionnaire.Id,
|
||||
UserName = questionnaire.UserName,
|
||||
UserEmail = questionnaire.Email,
|
||||
SubmissionDate = DateTime.UtcNow,
|
||||
SubmissionDate = cetTime,
|
||||
ResponseDetails = questionnaire.Questions.Select(q => new ResponseDetail
|
||||
{
|
||||
QuestionId = q.Id,
|
||||
|
|
@ -199,6 +204,8 @@ namespace Web.Controllers
|
|||
|
||||
TempData["UserName"] = questionnaire.UserName;
|
||||
|
||||
_hubContext.Clients.All.SendAsync("ReceiveNotification", questionnaire.UserName, questionnaire.Email);
|
||||
|
||||
return RedirectToAction(nameof(ThankYou));
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ builder.Services.MailStatConfiguration();
|
|||
builder.Services.ConfigureDashboard();
|
||||
builder.Services.UserResponseConfiguration();
|
||||
builder.Services.ConfigureOpenAI(config);
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
|
||||
|
||||
|
|
@ -88,4 +89,6 @@ app.MapControllerRoute(
|
|||
pattern:"{controller=Home}/{action=Index}/{id?}");
|
||||
|
||||
|
||||
app.MapHub<NotificationHub>("/notificationHub");
|
||||
|
||||
app.Run();
|
||||
|
|
|
|||
|
|
@ -5,5 +5,11 @@
|
|||
public Dictionary<string, int>? ModelCounts { get; set; }
|
||||
public Dictionary<string, int>? BannerSelections { 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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
Web/ViewModel/DashboardVM/PerformanceDataViewModel.cs
Normal file
9
Web/ViewModel/DashboardVM/PerformanceDataViewModel.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
10
Web/ViewModel/DashboardVM/VisitorDataViewModel.cs
Normal file
10
Web/ViewModel/DashboardVM/VisitorDataViewModel.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
namespace Web.ViewModel.DashboardVM
|
||||
{
|
||||
public class VisitorDataViewModel
|
||||
{
|
||||
|
||||
public string? Time { get; set; }
|
||||
public int VisitorCount { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
<PackageReference Include="MailJet.Api" Version="3.0.0" />
|
||||
<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.SignalR" Version="1.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue