diff --git a/Services/Implemnetation/DashboardRepository.cs b/Services/Implemnetation/DashboardRepository.cs new file mode 100644 index 0000000..6ac2f37 --- /dev/null +++ b/Services/Implemnetation/DashboardRepository.cs @@ -0,0 +1,48 @@ +using Data; +using Microsoft.EntityFrameworkCore; +using Services.Interaces; + +namespace Services.Implemnetation +{ + public class DashboardRepository : IDashboardRepository + { + private readonly SurveyContext _context; + + public DashboardRepository(SurveyContext Context) + { + _context = Context; + } + public async Task> GetModelCountsAsync() + { + var counts = new Dictionary + { + { "Pages", await _context.Pages.CountAsync() }, + { "Banners", await _context.Banners.CountAsync() }, + { "Addresses", await _context.Addresss.CountAsync() }, + { "Footers", await _context.Footers.CountAsync() }, + { "SocialMedia", await _context.SocialMedia.CountAsync() }, + { "FooterSocialMedias", await _context.FooterSocialMedias.CountAsync() }, + { "Subscriptions", await _context.Subscriptions.CountAsync() }, + { "SentNewsletterEmails", await _context.SentNewsletterEamils.CountAsync() } + }; + + return counts; + } + + public async Task> GetCurrentBannerSelectionsAsync() + { + return await _context.Pages + .GroupBy(p => p.banner.Title) + .Select(g => new { BannerId = g.Key, Count = g.Count() }) + .ToDictionaryAsync(g => g.BannerId.ToString(), g => g.Count); + } + + public async Task> GetCurrentFooterSelectionsAsync() + { + return await _context.Pages + .GroupBy(p => p.footer.Title) + .Select(g => new { FooterId = g.Key, Count = g.Count() }) + .ToDictionaryAsync(g => g.FooterId.ToString(), g => g.Count); + } + } +} diff --git a/Services/Implemnetation/UserResponseRepository.cs b/Services/Implemnetation/UserResponseRepository.cs new file mode 100644 index 0000000..82d059b --- /dev/null +++ b/Services/Implemnetation/UserResponseRepository.cs @@ -0,0 +1,33 @@ +using Data; +using Microsoft.EntityFrameworkCore; +using Model; +using Services.Interaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Services.Implemnetation +{ + public class UserResponseRepository : IUserResponseRepository + { + private readonly SurveyContext _context; + + public UserResponseRepository(SurveyContext context) + { + _context = context; + } + public async Task> GetResponsesByUserAsync(string userName) + { + return await _context.Responses + .Include(r => r.Questionnaire) + .Include(r => r.ResponseDetails) + .ThenInclude(rd => rd.Question) + .Include(r => r.ResponseDetails) + .ThenInclude(rd => rd.ResponseAnswers) + .Where(r => r.UserName == userName) + .ToListAsync(); + } + } +} diff --git a/Services/Interaces/IDashboardRepository.cs b/Services/Interaces/IDashboardRepository.cs new file mode 100644 index 0000000..37f785c --- /dev/null +++ b/Services/Interaces/IDashboardRepository.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Services.Interaces +{ + public interface IDashboardRepository + { + Task> GetModelCountsAsync(); + Task> GetCurrentBannerSelectionsAsync(); + Task> GetCurrentFooterSelectionsAsync(); + } +} diff --git a/Services/Interaces/IUserResponseRepository.cs b/Services/Interaces/IUserResponseRepository.cs new file mode 100644 index 0000000..e1772b9 --- /dev/null +++ b/Services/Interaces/IUserResponseRepository.cs @@ -0,0 +1,13 @@ +using Model; + + + + +namespace Services.Interaces +{ + public interface IUserResponseRepository + { + Task> GetResponsesByUserAsync(string userName); + + } +} diff --git a/Web/Areas/Admin/Controllers/AdminController.cs b/Web/Areas/Admin/Controllers/AdminController.cs index 9a321ac..b14c915 100644 --- a/Web/Areas/Admin/Controllers/AdminController.cs +++ b/Web/Areas/Admin/Controllers/AdminController.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Model; +using Services.Interaces; +using Web.ViewModel.DashboardVM; namespace Web.Areas.Admin.Controllers { @@ -11,14 +13,27 @@ namespace Web.Areas.Admin.Controllers public class AdminController : Controller { private readonly SignInManager _signInManager; + private readonly IDashboardRepository _dashboard; - public AdminController(SignInManager signInManager) + public AdminController(SignInManager signInManager,IDashboardRepository dashboard) { _signInManager = signInManager; + _dashboard = dashboard; } - public IActionResult Index() + public async Task Index() { - return View(); + var modelCounts = await _dashboard.GetModelCountsAsync(); + var bannerSelections = await _dashboard.GetCurrentBannerSelectionsAsync(); + var footerSelections = await _dashboard.GetCurrentFooterSelectionsAsync(); + + var viewModel = new DashboardViewModel + { + ModelCounts = modelCounts, + BannerSelections = bannerSelections, + FooterSelections = footerSelections + }; + + return View(viewModel); } [HttpPost] diff --git a/Web/Areas/Admin/Controllers/NewslettersController.cs b/Web/Areas/Admin/Controllers/NewslettersController.cs index 5113697..6be2916 100644 --- a/Web/Areas/Admin/Controllers/NewslettersController.cs +++ b/Web/Areas/Admin/Controllers/NewslettersController.cs @@ -14,6 +14,9 @@ using Services.Implemnetation; using Services.Interaces; using Web.AIConfiguration; using Web.ViewModel.NewsLetterVM; +using iText.Kernel.Pdf; +using iText.Kernel.Pdf.Canvas.Parser; +using System.Text.RegularExpressions; namespace Web.Areas.Admin.Controllers { @@ -35,13 +38,21 @@ namespace Web.Areas.Admin.Controllers _configuration = configuration; } - public IActionResult Index() + public IActionResult Index(int page = 1) { - var totalSubscribedUsers = _context.Subscriptions.Count(s => s.IsSubscribed); + const int PageSize = 10; - // Pass the total count to the view + var totalSubscribedUsers = _context.Subscriptions.Count(s => s.IsSubscribed); ViewBag.TotalSubscribedUsers = totalSubscribedUsers; - var newsLetterFromdb = _repository.GetAll(); + + var totalSubscriptions = _context.Subscriptions.Count(); + var totalPages = (int)Math.Ceiling(totalSubscriptions / (double)PageSize); + + var newsLetterFromdb = _repository.GetAll() + .OrderByDescending(x=>x.Id) + .Skip((page - 1) * PageSize) + .Take(PageSize) + .ToList(); var viewmodel = new List(); @@ -49,13 +60,21 @@ namespace Web.Areas.Admin.Controllers { viewmodel.Add(new NewsLetterViewModel { - Id=item.Id, - Name=item.Name, - Email=item.Email, - IsSubscribed=item.IsSubscribed + Id = item.Id, + Name = item.Name, + Email = item.Email, + IsSubscribed = item.IsSubscribed }); } - return View(viewmodel); + + var listViewModel = new PaginationViewModel + { + Subscriptions = viewmodel, + CurrentPage = page, + TotalPages = totalPages + }; + + return View(listViewModel); } public IActionResult Create() @@ -70,7 +89,21 @@ namespace Web.Areas.Admin.Controllers [HttpPost] - + public IActionResult DeleteSelectedSubscription(List selectedIds) + { + if (selectedIds != null && selectedIds.Any()) + { + var subscriptions = _context.Subscriptions.Where(s => selectedIds.Contains(s.Id)).ToList(); + _context.Subscriptions.RemoveRange(subscriptions); + _context.SaveChanges(); + } + TempData["Success"] = "Subscriber deleted successfully"; + return RedirectToAction(nameof(Index)); + } + + + + [HttpPost] public async Task Create(SendNewsLetterViewModel viewModel) { if (ModelState.IsValid) @@ -183,10 +216,77 @@ namespace Web.Areas.Admin.Controllers return View(viewModel); } - + [HttpGet] + public IActionResult UploadSubscribers() + { + return View(); + } [HttpPost] - public async Task MailjetWebhook() + public async Task UploadSubscribers(PdfUploadViewModel viewModel) + { + if (!ModelState.IsValid) + { + TempData["error"] = "Invalid model state."; + return BadRequest("Invalid model state."); + } + + if (viewModel.SubscriberFile == null || viewModel.SubscriberFile.Length == 0) + { + TempData["error"] = "No file uploaded or file is empty."; + return BadRequest("No file uploaded or file is empty."); + } + + var newSubscribers = new List(); + try + { + using (var pdfReader = new PdfReader(viewModel.SubscriberFile.OpenReadStream())) + using (var pdfDocument = new PdfDocument(pdfReader)) + { + for (int page = 1; page <= pdfDocument.GetNumberOfPages(); page++) + { + var text = PdfTextExtractor.GetTextFromPage(pdfDocument.GetPage(page)); + var matches = Regex.Matches(text, @"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", RegexOptions.IgnoreCase); + foreach (Match match in matches) + { + var email = match.Value.ToLower(); + var name = email.Split('@')[0].Replace(".", " ").Replace("_", " "); + if (!newSubscribers.Any(s => s.Email == email)) + { + newSubscribers.Add(new Subscription { Email = email, Name = name, IsSubscribed = true }); + } + } + } + } + + // Optional: Check existing emails to avoid duplicates + var existingEmails = _context.Subscriptions.Select(s => s.Email).ToHashSet(); + newSubscribers = newSubscribers.Where(s => !existingEmails.Contains(s.Email)).ToList(); + + if (newSubscribers.Any()) + { + _context.Subscriptions.AddRange(newSubscribers); + await _context.SaveChangesAsync(); + TempData["success"] = $"{newSubscribers.Count} new subscribers added successfully."; + return Ok($"{newSubscribers.Count} new subscribers added successfully."); + } + else + { + TempData["info"] = "No new subscribers found in the file."; + return Ok("No new subscribers found in the file."); + } + } + catch (Exception ex) + { + TempData["error"] = $"Error processing file: {ex.Message}"; + return BadRequest($"Error processing file: {ex.Message}"); + } + } + + + + [HttpPost] + public async Task MailTracking() { using (var reader = new StreamReader(Request.Body)) { diff --git a/Web/Areas/Admin/Controllers/UserResponseController.cs b/Web/Areas/Admin/Controllers/UserResponseController.cs index 7e2ba8e..fa3bd50 100644 --- a/Web/Areas/Admin/Controllers/UserResponseController.cs +++ b/Web/Areas/Admin/Controllers/UserResponseController.cs @@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Model; +using Services.Interaces; +using Web.ViewModel.QuestionnaireVM; namespace Web.Areas.Admin.Controllers { @@ -11,10 +13,12 @@ namespace Web.Areas.Admin.Controllers public class UserResponseController : Controller { private readonly SurveyContext _context; + private readonly IUserResponseRepository _userResponse; - public UserResponseController(SurveyContext context) + public UserResponseController(SurveyContext context, IUserResponseRepository userResponse) { _context = context; + _userResponse = userResponse; } public async Task Index() { @@ -51,6 +55,31 @@ namespace Web.Areas.Admin.Controllers } + + public async Task UserResponsesStatus(string userName) + { + var responses = await _userResponse.GetResponsesByUserAsync(userName); + + if (responses == null || !responses.Any()) + { + return NotFound(); + } + + var userEmail = responses.First().UserEmail; + + var viewModel = new UserResponsesViewModel + { + UserName = userName, + UserEmail = userEmail, + Responses = responses.ToList() + }; + + return View(viewModel); + } + + + + [HttpPost] [ValidateAntiForgeryToken] public async Task Delete(int id) diff --git a/Web/Areas/Admin/Controllers/UserResponseStatusController.cs b/Web/Areas/Admin/Controllers/UserResponseStatusController.cs new file mode 100644 index 0000000..eafd36b --- /dev/null +++ b/Web/Areas/Admin/Controllers/UserResponseStatusController.cs @@ -0,0 +1,100 @@ +using Data; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Model; +using Services.Interaces; +using Web.ViewModel.QuestionnaireVM; + +namespace Web.Areas.Admin.Controllers +{ + public class UserResponseStatusController : Controller + { + private readonly SurveyContext _context; + private readonly IUserResponseRepository _userResponse; + + public UserResponseStatusController(SurveyContext context,IUserResponseRepository userResponse) + { + _context = context; + _userResponse = userResponse; + } + public async Task Index() + { + var usersWithQuestionnaires = await _context.Responses + .Include(r => r.Questionnaire) + .GroupBy(r => r.UserEmail) + .Select(g => new UserResponsesViewModel + { + UserName = g.FirstOrDefault().UserName, // Display the first username found for the email + UserEmail = g.Key, + Responses = g.Select(r => new Response + { + Questionnaire = r.Questionnaire + }).Distinct().ToList() + }) + .ToListAsync(); + + return View(usersWithQuestionnaires); + } + + + public async Task UserResponsesStatus(string userEmail) + { + var responses = await _context.Responses + .Include(r => r.Questionnaire) + .Include(r => r.ResponseDetails) + .ThenInclude(rd => rd.Question) + .ThenInclude(q => q.Answers) // Include the Answers entity + .Include(r => r.ResponseDetails) + .ThenInclude(rd => rd.ResponseAnswers) + .Where(r => r.UserEmail == userEmail) + .ToListAsync(); + + if (responses == null || !responses.Any()) + { + return NotFound(); + } + + var userName = responses.First().UserName; + + var viewModel = new UserResponsesViewModel + { + UserName = userName, + UserEmail = userEmail, + Responses = responses + }; + + return View(viewModel); + } + + + //public async Task UserResponsesStatus(string userEmail) + //{ + // var responses = await _context.Responses + // .Include(r => r.Questionnaire) + // .Include(r => r.ResponseDetails) + // .ThenInclude(rd => rd.Question) + // .Include(r => r.ResponseDetails) + + // .ThenInclude(rd => rd.ResponseAnswers) + // .Where(r => r.UserEmail == userEmail) + // .ToListAsync(); + + // if (responses == null || !responses.Any()) + // { + // return NotFound(); + // } + + // var userName = responses.First().UserName; + + // var viewModel = new UserResponsesViewModel + // { + // UserName = userName, + // UserEmail = userEmail, + // Responses = responses + // }; + + // return View(viewModel); + //} + + } +} diff --git a/Web/Areas/Admin/Views/Admin/Index.cshtml b/Web/Areas/Admin/Views/Admin/Index.cshtml index 5f28270..15e61c2 100644 --- a/Web/Areas/Admin/Views/Admin/Index.cshtml +++ b/Web/Areas/Admin/Views/Admin/Index.cshtml @@ -1 +1,68 @@ - \ No newline at end of file +@model DashboardViewModel + +

Admin Dashboard

+ +
+
+
+ + diff --git a/Web/Areas/Admin/Views/Shared/_AdminLayout.cshtml b/Web/Areas/Admin/Views/Shared/_AdminLayout.cshtml index 9c34cfc..7e801df 100644 --- a/Web/Areas/Admin/Views/Shared/_AdminLayout.cshtml +++ b/Web/Areas/Admin/Views/Shared/_AdminLayout.cshtml @@ -46,6 +46,10 @@ Response +
  • + + User status +
  • Subscibers
  • diff --git a/Web/Areas/Admin/Views/Shared/_Notification.cshtml b/Web/Areas/Admin/Views/Shared/_Notification.cshtml index f0f55f7..3719493 100644 --- a/Web/Areas/Admin/Views/Shared/_Notification.cshtml +++ b/Web/Areas/Admin/Views/Shared/_Notification.cshtml @@ -1,4 +1,4 @@ -@if (TempData["Success"] != null) +@if (TempData["success"] != null) { @@ -6,10 +6,21 @@ + +} +@if (TempData["error"] != null) +{ + + + + + - + } \ No newline at end of file diff --git a/Web/Areas/Admin/Views/UserResponse/UserResponsesStatus.cshtml b/Web/Areas/Admin/Views/UserResponse/UserResponsesStatus.cshtml new file mode 100644 index 0000000..b74cfd8 --- /dev/null +++ b/Web/Areas/Admin/Views/UserResponse/UserResponsesStatus.cshtml @@ -0,0 +1,43 @@ +@model UserResponsesViewModel + +@{ + ViewData["Title"] = "User Responses"; +} + +

    User Responses

    + +
    +

    @Model.UserName (@Model.UserEmail)

    +
    + +@foreach (var response in Model.Responses) +{ +
    +

    Questionnaire: @response.Questionnaire.Title

    +

    Submitted on: @response.SubmissionDate

    + +
      + @foreach (var detail in response.ResponseDetails) + { +
    • + Question: @detail.Question.Text + @if (detail.QuestionType == QuestionType.Text) + { +

      Answer: @detail.TextResponse

      + } + else + { +
        + @foreach (var answer in detail.ResponseAnswers) + { +
      • Answer ID: @answer.AnswerId
      • + } +
      + } +
    • + } +
    +
    +} + + diff --git a/Web/Areas/Admin/Views/UserResponseStatus/Index.cshtml b/Web/Areas/Admin/Views/UserResponseStatus/Index.cshtml new file mode 100644 index 0000000..dfde84e --- /dev/null +++ b/Web/Areas/Admin/Views/UserResponseStatus/Index.cshtml @@ -0,0 +1,62 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "User Responses status"; +} + + + +
    +
    + +
    + +

    Response status

    + +
    + + + + + + + + + + + + + + @foreach (var item in Model) + { + + + + + + + + + } + +
    NameEmailSurveyAction
    @item.UserName@item.UserEmail +
      + @foreach (var response in item.Responses) + { + @response.Questionnaire.Title + } +
    +
    + View Responses status +
    + +
    + +
    + +
    +
    + + + + diff --git a/Web/Areas/Admin/Views/UserResponseStatus/UserResponsesStatus.cshtml b/Web/Areas/Admin/Views/UserResponseStatus/UserResponsesStatus.cshtml new file mode 100644 index 0000000..a2dae27 --- /dev/null +++ b/Web/Areas/Admin/Views/UserResponseStatus/UserResponsesStatus.cshtml @@ -0,0 +1,292 @@ +@model UserResponsesViewModel + +@{ + ViewData["Title"] = "User Responses"; +} + + + + + +
    +

    + Back to list +

    + +
    +
    +

    User Responses

    + @Model.UserName (@Model.UserEmail) +
    + + +
    + @foreach (var response in Model.Responses) + { +
    +
    + @response.Questionnaire.Title +
    +
    + +
    +
    +
    Survey: @response.Questionnaire.Title
    +

    Submitted on: @response.SubmissionDate

    +
    + + + + + +
    + + + + + + + + + @foreach (var detail in response.ResponseDetails) + { + + + + + } + +
    QuestionResponse
    @detail.Question.Text + @if (detail.QuestionType == QuestionType.Text || detail.QuestionType == QuestionType.Slider || detail.QuestionType == QuestionType.Open_ended) + { +
      +
    • + Question type + @detail.QuestionType +
    • +
    +
    +
      +
    • + Answer + @detail.TextResponse +
    • +
    + } + else + { +
      +
    • + Question type + @detail.QuestionType +
    • +
    +
    +
      + @foreach (var answer in detail.ResponseAnswers) + { +
    • + Answer + @detail.Question.Answers.FirstOrDefault(a => a.Id == answer.AnswerId)?.Text +
    • + } +
    + } +
    +
    +
    + +
    +
    + } +
    +
    +
    + + +@*
    +

    + Back to list +

    + +
    +
    +

    User Responses

    + @Model.UserName (@Model.UserEmail) +
    + +
    + @foreach (var response in Model.Responses) + { +
    +
    +
    Survey: @response.Questionnaire.Title
    +

    Submitted on: @response.SubmissionDate

    + + + + + +
    + + + + + + + + + @foreach (var detail in response.ResponseDetails) + { + + + + + } + +
    QuestionResponse
    @detail.Question.Text + @if (detail.QuestionType == QuestionType.Text || detail.QuestionType == QuestionType.Slider || detail.QuestionType == QuestionType.Open_ended) + { +
      +
    • + Question type + @detail.QuestionType +
    • + +
    +
    + +
      +
    • + + Answer + @detail.TextResponse +
    • +
    + + } + else + { + +
      +
    • + Question type + @detail.QuestionType +
    • + +
    +
    + +
      + + @foreach (var answer in detail.ResponseAnswers) + { + + +
    • + Answer + @detail.Question.Answers.FirstOrDefault(a => a.Id == answer.AnswerId)?.Text +
    • + } + +
    + + } +
    +
    +
    +
    + } +
    +
    +
    *@ + +@section Scripts { + + + +} diff --git a/Web/Areas/Admin/Views/_ViewImports.cshtml b/Web/Areas/Admin/Views/_ViewImports.cshtml index 406aaa3..da91766 100644 --- a/Web/Areas/Admin/Views/_ViewImports.cshtml +++ b/Web/Areas/Admin/Views/_ViewImports.cshtml @@ -10,6 +10,7 @@ @using Web.ViewModel.PageVM @using Web.ViewModel.AccountVM @using Web.ViewModel.NewsLetterVM +@using Web.ViewModel.DashboardVM @using Services.EmailSend @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/Web/Areas/Admin/Views/newsletters/Index.cshtml b/Web/Areas/Admin/Views/newsletters/Index.cshtml index 1065b50..c764e6f 100644 --- a/Web/Areas/Admin/Views/newsletters/Index.cshtml +++ b/Web/Areas/Admin/Views/newsletters/Index.cshtml @@ -1,8 +1,10 @@ -@model IEnumerable +@model PaginationViewModel @{ ViewData["Title"] = "Newsletter list"; } + + + + +
    +
    +
    + + +
    + +
    + + +
    + +@section Scripts { + @{ + + } + + +} diff --git a/Web/Extesions/ServicesExtesions.cs b/Web/Extesions/ServicesExtesions.cs index 60e849e..2217455 100644 --- a/Web/Extesions/ServicesExtesions.cs +++ b/Web/Extesions/ServicesExtesions.cs @@ -53,6 +53,10 @@ namespace Web.Extesions { services.AddScoped(); } + public static void ConfigureDashboard(this IServiceCollection services) + { + services.AddScoped(); + } public static void ConfigureFooter(this IServiceCollection services) { @@ -74,6 +78,10 @@ namespace Web.Extesions { services.AddTransient(); } + public static void UserResponseConfiguration(this IServiceCollection services) + { + services.AddTransient(); + } public static void MailStatConfiguration(this IServiceCollection services) { services.AddTransient(); diff --git a/Web/Program.cs b/Web/Program.cs index 905b727..8bf04aa 100644 --- a/Web/Program.cs +++ b/Web/Program.cs @@ -38,6 +38,8 @@ builder.Services.AddTransient(); builder.Services.ConfigureNewsLetter(); builder.Services.MailConfiguration(); builder.Services.MailStatConfiguration(); +builder.Services.ConfigureDashboard(); +builder.Services.UserResponseConfiguration(); builder.Services.ConfigureOpenAI(config); diff --git a/Web/Properties/launchSettings.json b/Web/Properties/launchSettings.json index ef5ec7d..2d7cf23 100644 --- a/Web/Properties/launchSettings.json +++ b/Web/Properties/launchSettings.json @@ -9,15 +9,7 @@ } }, "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "applicationUrl": "http://localhost:5205", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, + "https": { "commandName": "Project", "dotnetRunMessages": true, diff --git a/Web/ViewModel/DashboardVM/DashboardViewModel.cs b/Web/ViewModel/DashboardVM/DashboardViewModel.cs new file mode 100644 index 0000000..2f059c7 --- /dev/null +++ b/Web/ViewModel/DashboardVM/DashboardViewModel.cs @@ -0,0 +1,9 @@ +namespace Web.ViewModel.DashboardVM +{ + public class DashboardViewModel + { + public Dictionary? ModelCounts { get; set; } + public Dictionary? BannerSelections { get; set; } + public Dictionary? FooterSelections { get; set; } + } +} diff --git a/Web/ViewModel/NewsLetterVM/PaginationViewModel.cs b/Web/ViewModel/NewsLetterVM/PaginationViewModel.cs new file mode 100644 index 0000000..a29da5b --- /dev/null +++ b/Web/ViewModel/NewsLetterVM/PaginationViewModel.cs @@ -0,0 +1,11 @@ +using Model; + +namespace Web.ViewModel.NewsLetterVM +{ + public class PaginationViewModel + { + public IEnumerable? Subscriptions { get; set; } + public int CurrentPage { get; set; } + public int TotalPages { get; set; } + } +} diff --git a/Web/ViewModel/NewsLetterVM/PdfUploadViewModel.cs b/Web/ViewModel/NewsLetterVM/PdfUploadViewModel.cs new file mode 100644 index 0000000..7c479e1 --- /dev/null +++ b/Web/ViewModel/NewsLetterVM/PdfUploadViewModel.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; +namespace Web.ViewModel.NewsLetterVM +{ + public class PdfUploadViewModel + { + [Required(ErrorMessage = "Please upload a file.")] + public IFormFile SubscriberFile { get; set; } + } +} diff --git a/Web/ViewModel/QuestionnaireVM/UserResponsesViewModel.cs b/Web/ViewModel/QuestionnaireVM/UserResponsesViewModel.cs new file mode 100644 index 0000000..f4eea77 --- /dev/null +++ b/Web/ViewModel/QuestionnaireVM/UserResponsesViewModel.cs @@ -0,0 +1,13 @@ + + +using Model; + +namespace Web.ViewModel.QuestionnaireVM +{ + public class UserResponsesViewModel + { + public string? UserName { get; set; } + public string? UserEmail { get; set; } + public List? Responses { get; set; } + } +} diff --git a/Web/Views/QuestionnaireResponse/DisplayQuestionnaire.cshtml b/Web/Views/QuestionnaireResponse/DisplayQuestionnaire.cshtml index 69e93bb..483da39 100644 --- a/Web/Views/QuestionnaireResponse/DisplayQuestionnaire.cshtml +++ b/Web/Views/QuestionnaireResponse/DisplayQuestionnaire.cshtml @@ -320,15 +320,23 @@

    @Model.Title

    @Html.Raw(Model.Description)

    -
    - + + +
    +
    +
    +
    +
    +
    -
    - +
    +
    -
    +
    +
    +
    diff --git a/Web/Web.csproj b/Web/Web.csproj index 41b0d97..890ab12 100644 --- a/Web/Web.csproj +++ b/Web/Web.csproj @@ -12,6 +12,7 @@ + diff --git a/Web/appsettings.json b/Web/appsettings.json index 9e950a2..3028c58 100644 --- a/Web/appsettings.json +++ b/Web/appsettings.json @@ -21,7 +21,7 @@ }, "MailJet": { "ApiKey": "f545eee3a4743464b9d25fb9c5ab3f6c", - "SecretKey": "9fa430ef00873fdefe333fdc40ee3f8f" + "SecretKey": "8df3cf0337a090b1d6301f312ca51413" }, "OpenAI": { "ApiKey": "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" diff --git a/Web/wwwroot/css/site.css b/Web/wwwroot/css/site.css index fbd72e9..d19043a 100644 --- a/Web/wwwroot/css/site.css +++ b/Web/wwwroot/css/site.css @@ -41,6 +41,80 @@ .navbar-light .navbar-text{ color:white !important; } + +.bd-callout { + padding: 1.25rem; + margin-top: 1.25rem; + margin-bottom: 1.25rem; + border: 1px solid #eee; + border-left-width: .25rem; + border-radius: .25rem +} + + .bd-callout h4 { + margin-top: 0; + margin-bottom: .25rem + } + + .bd-callout p:last-child { + margin-bottom: 0 + } + + .bd-callout code { + border-radius: .25rem + } + + .bd-callout + .bd-callout { + margin-top: -.25rem + } + +.bd-callout-info { + border-left-color: #5bc0de +} + + .bd-callout-info h4 { + color: #5bc0de + } + +.bd-callout-warning { + border-left-color: #f0ad4e +} + + .bd-callout-warning h4 { + color: #f0ad4e + } + +.bd-callout-danger { + border-left-color: #d9534f +} + + .bd-callout-danger h4 { + color: #d9534f + } + +.bd-callout-primary { + border-left-color: #007bff +} + +.bd-callout-primaryh4 { + color: #007bff +} + +.bd-callout-success { + border-left-color: #28a745 +} + +.bd-callout-successh4 { + color: #28a745 +} + +.bd-callout-default { + border-left-color: #6c757d +} + +.bd-callout-defaulth4 { + color: #6c757d +} .MainBanner { display: flex; flex-wrap: wrap;