Add 'Other' option to questionnaire for custom user input

This commit is contained in:
Qaisyousuf 2025-08-14 15:07:48 +02:00
parent 908e241fd8
commit d9f9b600d7
16 changed files with 3769 additions and 492 deletions

View file

@ -21,6 +21,7 @@ namespace Model
[ForeignKey("QuestionId")] [ForeignKey("QuestionId")]
public Question? Question { get; set; } public Question? Question { get; set; }
public bool IsOtherOption { get; set; } = false;
public string? ConditionJson { get; set; } public string? ConditionJson { get; set; }
} }
} }

View file

@ -23,7 +23,7 @@ namespace Model
public QuestionType QuestionType { get; set; } public QuestionType QuestionType { get; set; }
public string? TextResponse { get; set; } public string? TextResponse { get; set; }
public List<ResponseAnswer> ResponseAnswers { get; set; } = new List<ResponseAnswer>(); public List<ResponseAnswer> ResponseAnswers { get; set; } = new List<ResponseAnswer>();
public string? OtherText { get; set; }
public ResponseStatus Status { get; set; } = ResponseStatus.Shown; public ResponseStatus Status { get; set; } = ResponseStatus.Shown;
public string? SkipReason { get; set; } // Why it was skipped (JSON of condition) public string? SkipReason { get; set; } // Why it was skipped (JSON of condition)
} }

View file

@ -93,12 +93,8 @@ namespace Web.Areas.Admin.Controllers
[HttpPost] [HttpPost]
public async Task<IActionResult> Create(QuestionnaireViewModel viewmodel) public async Task<IActionResult> Create(QuestionnaireViewModel viewmodel)
{ {
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
var questionnaire = new Questionnaire var questionnaire = new Questionnaire
{ {
Id = viewmodel.Id, Id = viewmodel.Id,
@ -106,7 +102,6 @@ namespace Web.Areas.Admin.Controllers
Description = viewmodel.Description, Description = viewmodel.Description,
}; };
var questions = viewmodel.Questions; var questions = viewmodel.Questions;
foreach (var questionViewModel in viewmodel.Questions) foreach (var questionViewModel in viewmodel.Questions)
@ -121,30 +116,27 @@ namespace Web.Areas.Admin.Controllers
foreach (var answerViewModel in questionViewModel.Answers) foreach (var answerViewModel in questionViewModel.Answers)
{ {
// Skip empty answers
if (string.IsNullOrWhiteSpace(answerViewModel.Text))
continue;
var answer = new Answer var answer = new Answer
{ {
Text = answerViewModel.Text, Text = answerViewModel.Text,
QuestionId = answerViewModel.QuestionId, QuestionId = answerViewModel.QuestionId,
IsOtherOption = answerViewModel.IsOtherOption // NEW: Handle IsOtherOption property
}; };
question.Answers.Add(answer); question.Answers.Add(answer);
} }
questionnaire.Questions.Add(question); questionnaire.Questions.Add(question);
} }
_questionnaire.Add(questionnaire); _questionnaire.Add(questionnaire);
await _questionnaire.commitAsync(); await _questionnaire.commitAsync();
TempData["Success"] = "Questionnaire created successfully"; TempData["Success"] = "Questionnaire created successfully";
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
return View(viewmodel); return View(viewmodel);
@ -201,10 +193,9 @@ namespace Web.Areas.Admin.Controllers
[HttpPost] [HttpPost]
public async Task<IActionResult> Edit(EditQuestionnaireViewModel viewModel) public async Task<IActionResult> Edit(EditQuestionnaireViewModel viewModel)
{ {
var questionTypes = Enum.GetValues(typeof(QuestionType)) var questionTypes = Enum.GetValues(typeof(QuestionType))
.Cast<QuestionType>() .Cast<QuestionType>()
.Select(e => new SelectListItem { Value = e.ToString(), Text = e.ToString() }); .Select(e => new SelectListItem { Value = e.ToString(), Text = e.ToString() });
ViewBag.QuestionTypes = questionTypes; ViewBag.QuestionTypes = questionTypes;
if (ModelState.IsValid) if (ModelState.IsValid)
@ -214,7 +205,7 @@ namespace Web.Areas.Admin.Controllers
if (existingQuestionnaire == null) if (existingQuestionnaire == null)
{ {
return NotFound(); // Or handle not found case appropriately return NotFound();
} }
// Update the existing questionnaire with the data from the view model // Update the existing questionnaire with the data from the view model
@ -226,22 +217,13 @@ namespace Web.Areas.Admin.Controllers
// Iterate through existing questions and remove those not found in the view model // Iterate through existing questions and remove those not found in the view model
foreach (var existingQuestion in existingQuestionnaire.Questions.ToList()) foreach (var existingQuestion in existingQuestionnaire.Questions.ToList())
{ {
// If the ID of the existing question is not found in the view model, remove it
if (!viewModel.Questions.Any(q => q.Id == existingQuestion.Id)) if (!viewModel.Questions.Any(q => q.Id == existingQuestion.Id))
{ {
existingQuestionnaire.Questions.Remove(existingQuestion); existingQuestionnaire.Questions.Remove(existingQuestion);
} }
await _questionnaire.Update(existingQuestionnaire); await _questionnaire.Update(existingQuestionnaire);
} }
// Update the questionnaire with the modified list of questions
var newQuestions = new List<Question>(); var newQuestions = new List<Question>();
// Update or add new questions // Update or add new questions
@ -253,11 +235,9 @@ namespace Web.Areas.Admin.Controllers
{ {
if (existingQuestion != null) if (existingQuestion != null)
{ {
var answersToRemove = new List<Answer>();
existingQuestion.Text = questionViewModel.Text; existingQuestion.Text = questionViewModel.Text;
existingQuestion.Type = questionViewModel.Type; existingQuestion.Type = questionViewModel.Type;
foreach (var answerViewModel in questionViewModel.Answers) foreach (var answerViewModel in questionViewModel.Answers)
{ {
// Check if the answer already exists // Check if the answer already exists
@ -265,32 +245,27 @@ namespace Web.Areas.Admin.Controllers
if (answerViewModel.Id == 0) if (answerViewModel.Id == 0)
{ {
// NEW: Add IsOtherOption property when creating new answers
existingQuestion.Answers.Add(new Answer { Text = answerViewModel.Text }); existingQuestion.Answers.Add(new Answer
{
Text = answerViewModel.Text,
IsOtherOption = answerViewModel.IsOtherOption
});
} }
else if (answerViewModel.Text == null) else if (answerViewModel.Text == null)
{ {
existingQuestion.Answers.Remove(existingAnswer); existingQuestion.Answers.Remove(existingAnswer);
await _questionnaire.Update(existingQuestionnaire); await _questionnaire.Update(existingQuestionnaire);
} }
else if (existingAnswer != null) else if (existingAnswer != null)
{ {
existingAnswer.Text = answerViewModel.Text; existingAnswer.Text = answerViewModel.Text;
// NEW: Update IsOtherOption property for existing answers
existingAnswer.IsOtherOption = answerViewModel.IsOtherOption;
} }
} }
} }
} }
else else
{ {
// Create a new question // Create a new question
@ -305,52 +280,33 @@ namespace Web.Areas.Admin.Controllers
{ {
if (!string.IsNullOrEmpty(answerViewModel.Text)) if (!string.IsNullOrEmpty(answerViewModel.Text))
{ {
// Add new answer if text is not null or empty // NEW: Add IsOtherOption property when creating new answers
newQuestion.Answers.Add(new Answer { Text = answerViewModel.Text }); newQuestion.Answers.Add(new Answer
{
Text = answerViewModel.Text,
IsOtherOption = answerViewModel.IsOtherOption
});
} }
} }
// Add new question to the list of new questions
newQuestions.Add(newQuestion); newQuestions.Add(newQuestion);
} }
existingQuestionnaire.Questions.AddRange(newQuestions); existingQuestionnaire.Questions.AddRange(newQuestions);
//else
//{
// // Add new question
// var newQuestion = new Question
// {
// Text = questionViewModel.Text, // Make sure question text is not null
// Type = questionViewModel.Type, // Make sure question type is not null
// Answers = new List<Answer>() // Initialize answers list
// };
// foreach (var answerViewModel in questionViewModel.Answers)
// {
// // Add new answer
// newQuestion.Answers.Add(new Answer { Text = answerViewModel.Text });
// }
// // Add new question to questionnaire
// existingQuestionnaire.Questions.Add(newQuestion);
//}
} }
await _questionnaire.Update(existingQuestionnaire); await _questionnaire.Update(existingQuestionnaire);
TempData["Success"] = "Questionnaire updated successfully"; TempData["Success"] = "Questionnaire updated successfully";
return RedirectToAction(nameof(Index)); return RedirectToAction(nameof(Index));
} }
// If ModelState is not valid, re-display the form with validation errors
return View(viewModel); return View(viewModel);
} }
[HttpGet] [HttpGet]
public IActionResult Delete(int id) public IActionResult Delete(int id)
{ {
var questionTypes = Enum.GetValues(typeof(QuestionType)).Cast<QuestionType>(); var questionTypes = Enum.GetValues(typeof(QuestionType)).Cast<QuestionType>();
ViewBag.QuestionTypes = new SelectList(questionTypes); ViewBag.QuestionTypes = new SelectList(questionTypes);
@ -358,7 +314,7 @@ namespace Web.Areas.Admin.Controllers
if (questionnaire == null) if (questionnaire == null)
{ {
return NotFound(); // Or handle not found case appropriately return NotFound();
} }
var viewModel = new QuestionnaireViewModel var viewModel = new QuestionnaireViewModel
@ -375,7 +331,8 @@ namespace Web.Areas.Admin.Controllers
Answers = q.Answers.Select(a => new Answer Answers = q.Answers.Select(a => new Answer
{ {
Id = a.Id, Id = a.Id,
Text = a.Text Text = a.Text,
IsOtherOption = a.IsOtherOption // NEW: Include IsOtherOption property
}).ToList() }).ToList()
}).ToList() }).ToList()
}; };
@ -422,7 +379,7 @@ namespace Web.Areas.Admin.Controllers
if (questionnaire == null) if (questionnaire == null)
{ {
return NotFound(); // Or handle not found case appropriately return NotFound();
} }
var viewModel = new QuestionnaireViewModel var viewModel = new QuestionnaireViewModel
@ -439,7 +396,8 @@ namespace Web.Areas.Admin.Controllers
Answers = q.Answers.Select(a => new Answer Answers = q.Answers.Select(a => new Answer
{ {
Id = a.Id, Id = a.Id,
Text = a.Text Text = a.Text,
IsOtherOption = a.IsOtherOption // NEW: Include IsOtherOption property
}).ToList() }).ToList()
}).ToList() }).ToList()
}; };

View file

@ -8,8 +8,6 @@ using OfficeOpenXml;
using Services.Interaces; using Services.Interaces;
using Web.ViewModel.QuestionnaireVM; using Web.ViewModel.QuestionnaireVM;
namespace Web.Areas.Admin.Controllers namespace Web.Areas.Admin.Controllers
{ {
public class UserResponseStatusController : Controller public class UserResponseStatusController : Controller
@ -17,7 +15,7 @@ namespace Web.Areas.Admin.Controllers
private readonly SurveyContext _context; private readonly SurveyContext _context;
private readonly IUserResponseRepository _userResponse; private readonly IUserResponseRepository _userResponse;
public UserResponseStatusController(SurveyContext context,IUserResponseRepository userResponse) public UserResponseStatusController(SurveyContext context, IUserResponseRepository userResponse)
{ {
_context = context; _context = context;
_userResponse = userResponse; _userResponse = userResponse;
@ -41,7 +39,6 @@ namespace Web.Areas.Admin.Controllers
return View(usersWithQuestionnaires); return View(usersWithQuestionnaires);
} }
public async Task<IActionResult> UserResponsesStatus(string userEmail) public async Task<IActionResult> UserResponsesStatus(string userEmail)
{ {
var responses = await _context.Responses var responses = await _context.Responses
@ -72,8 +69,6 @@ namespace Web.Areas.Admin.Controllers
return View(viewModel); return View(viewModel);
} }
[HttpPost] [HttpPost]
public async Task<IActionResult> DeleteSelected(string[] selectedEmails) public async Task<IActionResult> DeleteSelected(string[] selectedEmails)
{ {
@ -95,8 +90,6 @@ namespace Web.Areas.Admin.Controllers
return RedirectToAction(nameof(Index)); return RedirectToAction(nameof(Index));
} }
public async Task<IActionResult> GenerateReport(string userEmail, string format) public async Task<IActionResult> GenerateReport(string userEmail, string format)
{ {
var responses = await _context.Responses var responses = await _context.Responses
@ -125,7 +118,6 @@ namespace Web.Areas.Admin.Controllers
} }
} }
private IActionResult GeneratePdfReport(List<Response> responses) private IActionResult GeneratePdfReport(List<Response> responses)
{ {
var userName = responses.First().UserName; var userName = responses.First().UserName;
@ -197,6 +189,15 @@ namespace Web.Areas.Admin.Controllers
{ {
table.AddCell(new PdfPCell(new Phrase("Answers:", cellFont)) { Padding = 5 }); 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)); var answers = string.Join(", ", detail.ResponseAnswers.Select(a => detail.Question.Answers.FirstOrDefault(ans => ans.Id == a.AnswerId)?.Text));
// NEW: Include "Other" text if available
if (!string.IsNullOrEmpty(detail.OtherText))
{
answers += string.IsNullOrEmpty(answers)
? $"Other: {detail.OtherText}"
: $"; Other: {detail.OtherText}";
}
table.AddCell(new PdfPCell(new Phrase(answers, cellFont)) { Padding = 5 }); table.AddCell(new PdfPCell(new Phrase(answers, cellFont)) { Padding = 5 });
} }
} }
@ -211,8 +212,6 @@ namespace Web.Areas.Admin.Controllers
return File(stream, "application/pdf", $"{userName}_report.pdf"); return File(stream, "application/pdf", $"{userName}_report.pdf");
} }
private IActionResult GenerateExcelReport(List<Response> responses) private IActionResult GenerateExcelReport(List<Response> responses)
{ {
var userName = responses.First().UserName; var userName = responses.First().UserName;
@ -272,6 +271,15 @@ namespace Web.Areas.Admin.Controllers
else else
{ {
var answers = string.Join(", ", detail.ResponseAnswers.Select(a => detail.Question.Answers.FirstOrDefault(ans => ans.Id == a.AnswerId)?.Text)); var answers = string.Join(", ", detail.ResponseAnswers.Select(a => detail.Question.Answers.FirstOrDefault(ans => ans.Id == a.AnswerId)?.Text));
// NEW: Include "Other" text if available
if (!string.IsNullOrEmpty(detail.OtherText))
{
answers += string.IsNullOrEmpty(answers)
? $"Other: {detail.OtherText}"
: $"; Other: {detail.OtherText}";
}
worksheet.Cells[row, 4].Value = answers; worksheet.Cells[row, 4].Value = answers;
} }
row++; row++;
@ -289,11 +297,6 @@ namespace Web.Areas.Admin.Controllers
} }
} }
public async Task<IActionResult> GenerateQuestionnairePdfReport(int questionnaireId) public async Task<IActionResult> GenerateQuestionnairePdfReport(int questionnaireId)
{ {
var response = await _context.Responses var response = await _context.Responses
@ -390,6 +393,15 @@ namespace Web.Areas.Admin.Controllers
{ {
table.AddCell(new PdfPCell(new Phrase("Answers:", cellFont)) { Padding = 5 }); 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)); var answers = string.Join(", ", detail.ResponseAnswers.Select(a => detail.Question.Answers.FirstOrDefault(ans => ans.Id == a.AnswerId)?.Text));
// NEW: Include "Other" text if available
if (!string.IsNullOrEmpty(detail.OtherText))
{
answers += string.IsNullOrEmpty(answers)
? $"Other: {detail.OtherText}"
: $"; Other: {detail.OtherText}";
}
table.AddCell(new PdfPCell(new Phrase(answers, cellFont)) { Padding = 5 }); table.AddCell(new PdfPCell(new Phrase(answers, cellFont)) { Padding = 5 });
} }
} }
@ -421,7 +433,6 @@ namespace Web.Areas.Admin.Controllers
return GenerateExcelReportForQuestionnaire(response); return GenerateExcelReportForQuestionnaire(response);
} }
private IActionResult GenerateExcelReportForQuestionnaire(Response response) private IActionResult GenerateExcelReportForQuestionnaire(Response response)
{ {
var userName = response.UserName; var userName = response.UserName;
@ -445,7 +456,7 @@ namespace Web.Areas.Admin.Controllers
worksheet.Cells[5, 1].Value = $"{userName} ({userEmail})"; worksheet.Cells[5, 1].Value = $"{userName} ({userEmail})";
worksheet.Cells[5, 1, 5, 4].Merge = true; 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.Size = 15;
worksheet.Cells[5, 1, 5, 4].Style.Font.Bold =true; worksheet.Cells[5, 1, 5, 4].Style.Font.Bold = true;
worksheet.Cells[5, 1, 5, 4].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center; worksheet.Cells[5, 1, 5, 4].Style.HorizontalAlignment = OfficeOpenXml.Style.ExcelHorizontalAlignment.Center;
// Add a title // Add a title
@ -486,6 +497,15 @@ namespace Web.Areas.Admin.Controllers
else else
{ {
var answers = string.Join(", ", detail.ResponseAnswers.Select(a => detail.Question.Answers.FirstOrDefault(ans => ans.Id == a.AnswerId)?.Text)); var answers = string.Join(", ", detail.ResponseAnswers.Select(a => detail.Question.Answers.FirstOrDefault(ans => ans.Id == a.AnswerId)?.Text));
// NEW: Include "Other" text if available
if (!string.IsNullOrEmpty(detail.OtherText))
{
answers += string.IsNullOrEmpty(answers)
? $"Other: {detail.OtherText}"
: $"; Other: {detail.OtherText}";
}
worksheet.Cells[row, 4].Value = answers; worksheet.Cells[row, 4].Value = answers;
} }
row++; row++;
@ -500,81 +520,5 @@ namespace Web.Areas.Admin.Controllers
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", $"{response.Questionnaire.Title}_{userEmail}.xlsx"); return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", $"{response.Questionnaire.Title}_{userEmail}.xlsx");
} }
} }
//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, 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++;
// 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");
// }
//}
} }
} }

View file

@ -1,10 +1,559 @@
 @model QuestionnaireViewModel
@model QuestionnaireViewModel
@{ @{
ViewData["Title"] = "Create"; ViewData["Title"] = "Create";
} }
<style>
/* Modern Design Enhancements */
@@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
:root {
--primary-color: #6366f1;
--primary-light: #8b5cf6;
--primary-dark: #4338ca;
--success-color: #10b981;
--danger-color: #ef4444;
--warning-color: #f59e0b;
--info-color: #06b6d4;
--gray-50: #f8fafc;
--gray-100: #f1f5f9;
--gray-200: #e2e8f0;
--gray-300: #cbd5e1;
--gray-400: #94a3b8;
--gray-500: #64748b;
--gray-600: #475569;
--gray-700: #334155;
--gray-800: #1e293b;
--gray-900: #0f172a;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
--border-radius-sm: 8px;
--border-radius-md: 12px;
--border-radius-lg: 16px;
--border-radius-xl: 20px;
}
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
body {
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
min-height: 100vh;
}
.container {
max-width: 1200px;
}
/* Enhanced Card Design */
.card {
background: white;
border-radius: var(--border-radius-xl);
box-shadow: var(--shadow-2xl);
border: 1px solid var(--gray-200);
overflow: hidden;
backdrop-filter: blur(10px);
}
.card-title {
font-size: 2rem;
font-weight: 700;
color: var(--gray-800);
text-align: center;
margin-bottom: 2rem;
position: relative;
padding-bottom: 1rem;
}
.card-title::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 4px;
background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
border-radius: 2px;
}
.card-body {
padding: 3rem;
background: var(--gray-50);
}
.card-footer {
background: white;
padding: 2.5rem;
border-top: 1px solid var(--gray-200);
display: flex;
gap: 1.5rem;
align-items: center;
flex-wrap: wrap;
}
/* Form Enhancements */
.mb-3 {
margin-bottom: 2rem !important;
}
.form-control {
border: 2px solid var(--gray-200);
border-radius: var(--border-radius-md);
padding: 1rem 1.25rem;
font-size: 0.95rem;
font-weight: 500;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: white;
box-shadow: var(--shadow-sm);
width: 100%;
min-height: 50px;
}
.form-control:focus {
border-color: var(--primary-color);
background: white;
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1), var(--shadow-md);
outline: none;
transform: translateY(-1px);
}
.control-label {
font-weight: 600;
color: var(--gray-700);
margin-bottom: 0.75rem;
font-size: 0.95rem;
display: flex;
align-items: center;
}
/* Enhanced Select Styling */
select.form-control {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-color: white;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.75rem center;
background-repeat: no-repeat;
background-size: 1.25em 1.25em;
padding-right: 2.5rem;
cursor: pointer;
}
select.form-control:focus {
background-color: white;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236366f1' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.75rem center;
background-repeat: no-repeat;
background-size: 1.25em 1.25em;
}
select.form-control option {
padding: 0.75rem;
font-weight: 500;
color: var(--gray-900);
background: white;
}
/* Textarea specific styling */
textarea.form-control {
min-height: 120px;
resize: vertical;
}
/* Button Enhancements */
.btn {
border-radius: var(--border-radius-md);
font-weight: 600;
padding: 0.75rem 1.5rem;
font-size: 0.875rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: none;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
box-shadow: var(--shadow-sm);
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: left 0.5s;
}
.btn:hover::before {
left: 100%;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
color: white;
}
.btn-primary:hover {
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
color: white;
}
.btn-success {
background: linear-gradient(135deg, var(--success-color) 0%, #059669 100%);
color: white;
}
.btn-success:hover {
background: linear-gradient(135deg, #059669 0%, #047857 100%);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
color: white;
}
.btn-danger {
background: linear-gradient(135deg, var(--danger-color) 0%, #dc2626 100%);
color: white;
}
.btn-danger:hover {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
color: white;
}
.btn-info {
background: linear-gradient(135deg, var(--info-color) 0%, #0891b2 100%);
color: white;
}
.btn-info:hover {
background: linear-gradient(135deg, #0891b2 0%, #0e7490 100%);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
color: white;
}
.btn-warning {
background: linear-gradient(135deg, var(--warning-color) 0%, #d97706 100%);
color: white;
}
.btn-warning:hover {
background: linear-gradient(135deg, #d97706 0%, #b45309 100%);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
color: white;
}
/* Questions Container */
#questions-container {
background: white;
border-radius: var(--border-radius-lg);
padding: 3rem;
margin: 2rem 0;
box-shadow: var(--shadow-xl);
border: 1px solid var(--gray-200);
position: relative;
overflow: hidden;
}
#questions-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--primary-color), var(--primary-light), var(--success-color));
}
#questions-container h3 {
font-size: 1.75rem;
font-weight: 700;
color: var(--gray-800);
margin-bottom: 3rem;
text-align: center;
}
/* Question Group Styling */
.question-group {
background: var(--gray-50);
border-radius: var(--border-radius-lg);
padding: 2.5rem;
margin-bottom: 2.5rem;
border: 2px solid var(--gray-200);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.question-group::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
transform: scaleY(0);
transition: transform 0.3s ease;
transform-origin: bottom;
}
.question-group:hover {
border-color: var(--primary-color);
box-shadow: var(--shadow-xl);
transform: translateY(-2px);
background: white;
}
.question-group:hover::before {
transform: scaleY(1);
}
.question-group.collapsed {
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
border-color: var(--success-color);
}
.question-group label {
font-size: 1.1rem;
font-weight: 600;
color: var(--gray-800);
margin-bottom: 1rem;
}
/* Answers Container */
.answers-container {
background: white;
border-radius: var(--border-radius-md);
padding: 2rem;
margin: 1.5rem 0;
border: 1px solid var(--gray-200);
box-shadow: var(--shadow-sm);
}
.answers-container label {
font-size: 1.25rem;
font-weight: 700;
color: var(--gray-800);
margin-bottom: 1.5rem;
}
/* Answer Group Styling */
.answer-group {
background: var(--gray-50);
border-radius: var(--border-radius-md);
padding: 1.5rem;
margin-bottom: 1.25rem;
border: 1px solid var(--gray-200);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.answer-group:hover {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
background: white;
}
/* Other Option Styling */
.other-option-group {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border: 2px solid var(--warning-color);
position: relative;
}
.other-option-group::before {
content: "OTHER OPTION";
position: absolute;
top: -12px;
left: 20px;
background: var(--warning-color);
color: white;
padding: 0.375rem 1rem;
border-radius: var(--border-radius-sm);
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.5px;
box-shadow: var(--shadow-sm);
}
/* Add Question Button */
#add-question-btn {
background: linear-gradient(135deg, var(--success-color) 0%, #059669 100%);
color: white;
padding: 1.25rem 2.5rem;
font-size: 1.1rem;
font-weight: 700;
border-radius: var(--border-radius-lg);
margin: 2rem auto;
box-shadow: var(--shadow-lg);
border: none;
display: block;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
#add-question-btn:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-2xl);
background: linear-gradient(135deg, #059669 0%, #047857 100%);
}
/* Alert Enhancements */
.alert {
border-radius: var(--border-radius-md);
padding: 1.25rem 1.5rem;
margin: 1.5rem 0;
border: none;
font-weight: 500;
box-shadow: var(--shadow-sm);
animation: slideDown 0.3s ease;
}
.alert-success {
background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
color: #065f46;
border-left: 4px solid var(--success-color);
}
.alert-info {
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
color: #0c4a6e;
border-left: 4px solid var(--info-color);
}
.alert-danger {
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
color: #991b1b;
border-left: 4px solid var(--danger-color);
}
/* Horizontal Rules */
hr {
border: none;
height: 2px;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
margin: 2rem 0;
border-radius: 1px;
}
hr.border-4 {
height: 4px;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
margin: 3rem 0;
}
/* Container Enhancements */
.container-ms {
background: white;
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-md);
border: 1px solid var(--gray-200);
}
/* Responsive Design */
@@media (max-width: 768px) {
.card-body
{
padding: 2rem;
}
#questions-container {
padding: 2rem;
}
.question-group {
padding: 2rem;
}
.card-footer {
flex-direction: column;
align-items: stretch;
}
.card-title {
font-size: 1.5rem;
}
}
/* Animations */
@@keyframes slideDown {
from
{
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
@@keyframes fadeIn {
from
{
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Enhanced Focus States */
.btn:focus,
.form-control:focus {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
/* Typography Enhancements */
h1, h2, h3, h4, h5, h6 {
font-weight: 700;
letter-spacing: -0.025em;
}
/* Icon styling */
.bi {
font-size: 1.1em;
}
/* Validation styling */
.text-danger {
color: var(--danger-color) !important;
font-weight: 500;
font-size: 0.875rem;
margin-top: 0.5rem;
}
</style>
<div class="container mt-4"> <div class="container mt-4">
<div class="card justify-content-center p-4 shadow rounded"> <div class="card justify-content-center p-4 shadow rounded">
<div class="card-body"> <div class="card-body">
@ -24,16 +573,16 @@
<textarea asp-for="Description" class="form-control"></textarea> <textarea asp-for="Description" class="form-control"></textarea>
<span asp-validation-for="Description" class="text-danger"></span> <span asp-validation-for="Description" class="text-danger"></span>
</div> </div>
<div class="container p-5 shadow bg-body-tertiary rounded"> <div class="container p-5 shadow bg-body-tertiary rounded" id="questions-container">
<div id="questions-container" class="mx-md-3 mx-lg-3 px-md-3 px-lg-3 mx-sm-0 px-sm-0"> <div class="mx-md-3 mx-lg-3 px-md-3 px-lg-3 mx-sm-0 px-sm-0">
<h3 class="text-primary font-weight-bold">Create Questions</h3> <h3 class="text-primary font-weight-bold">Create Questions</h3>
<div class="form-group"> <div class="form-group">
@for (int i = 0; i < Model.Questions?.Count; i++) @for (int i = 0; i < Model.Questions?.Count; i++)
{ {
<div class="question-group" data-question-index="@i"> <div class="question-group fade-in" data-question-index="@i">
<label>Question @(i + 1)</label> <label>Question @(i + 1)</label>
<textarea name="Questions[@i].Text" class="form-control">@Model.Questions[i].Text</textarea> <textarea name="Questions[@i].Text" class="form-control">@Model.Questions[i].Text</textarea>
<select name="Questions[@i].Type" asp-items="ViewBag.QuestionTypes" class="form-control question-type"> <select name="Questions[@i].Type" asp-items="ViewBag.QuestionTypes" class="form-control question-type">
<!-- Include options for question types... --> <!-- Include options for question types... -->
<div class="container-sm"></div> <div class="container-sm"></div>
@ -44,10 +593,12 @@
{ {
<div class="answer-group"> <div class="answer-group">
<input type="text" name="Questions[@i].Answers[@j].Text" class="form-control" value="@Model.Questions?[i]?.Answers?[j]?.Text" /> <input type="text" name="Questions[@i].Answers[@j].Text" class="form-control" value="@Model.Questions?[i]?.Answers?[j]?.Text" />
<input type="hidden" name="Questions[@i].Answers[@j].IsOtherOption" value="@Model.Questions?[i]?.Answers?[j]?.IsOtherOption" />
<button type="button" class="btn btn-sm btn-danger shadow remove-answer">Remove Answer</button> <button type="button" class="btn btn-sm btn-danger shadow remove-answer">Remove Answer</button>
</div> </div>
} }
<button type="button" class="btn btn-sm btn-success shadow add-answer"><i class="bi bi-plus-square"></i> Create Answer</button> <button type="button" class="btn btn-sm btn-success shadow add-answer"><i class="bi bi-plus-square"></i> Create Answer</button>
<button type="button" class="btn btn-sm btn-info shadow add-other-option"><i class="bi bi-pencil-square"></i> Add "Other" Option</button>
<input type="hidden" name="Questions[@i].Answers" /> <input type="hidden" name="Questions[@i].Answers" />
</div> </div>
<button type="button" class="btn btn-sm p-3 btn-danger shadow remove-question">Remove Question <i class="bi bi-trash3-fill"></i></button> | <button type="button" class="btn btn-sm p-3 btn-danger shadow remove-question">Remove Question <i class="bi bi-trash3-fill"></i></button> |
@ -85,14 +636,15 @@
<partial name="_ValidationScriptsPartial" /> <partial name="_ValidationScriptsPartial" />
} }
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
var questionIndex = @Model.Questions?.Count; var questionIndex = @Model.Questions?.Count;
$("#add-question-btn").click(function () { $("#add-question-btn").click(function () {
var newQuestionHtml = ` var newQuestionHtml = `
<div class="question-group" data-question-index="${questionIndex}"> <div class="question-group fade-in" data-question-index="${questionIndex}">
<label>Question ${questionIndex + 1}</label> <label>Question ${questionIndex + 1}</label>
<textarea name="Questions[${questionIndex}].Text" class="form-control" placeholder="new question"></textarea> <textarea name="Questions[${questionIndex}].Text" class="form-control" placeholder="new question"></textarea>
<br> <br>
@ -108,14 +660,15 @@
newQuestionHtml += `</select>`; newQuestionHtml += `</select>`;
// Add answers input fields // Add answers input fields
newQuestionHtml += `<div class="container-ms mx-5 py-2 px-5 ">`; newQuestionHtml += `<div class="container-ms mx-5 py-2 px-5 ">`;
newQuestionHtml += `<div class="answers-container" data-question-index="${questionIndex}"><br>`; newQuestionHtml += `<div class="answers-container" data-question-index="${questionIndex}"><br>`;
newQuestionHtml += `<label class="h3">Create Answers:</label>`; newQuestionHtml += `<label class="h3">Create Answers:</label>`;
newQuestionHtml += `<div class="answer-group" data-answer-index="0"><hr class="border border-primary border-1 opacity-35">`; newQuestionHtml += `<div class="answer-group" data-answer-index="0"><hr class="border border-primary border-1 opacity-35">`;
newQuestionHtml += `<label>Answer 1</label>`; newQuestionHtml += `<label>Answer 1</label>`;
newQuestionHtml += `<input type="text" name="Questions[${questionIndex}].Answers[0].Text" class="form-control" placeholder="new answer"/><br>`; newQuestionHtml += `<input type="text" name="Questions[${questionIndex}].Answers[0].Text" class="form-control" placeholder="new answer"/><br>`;
newQuestionHtml += `<input type="hidden" name="Questions[${questionIndex}].Answers[0].IsOtherOption" value="false"/>`;
newQuestionHtml += `<button type="button" class="btn btn-sm btn-success add-answer shadow mt-2"><i class="bi bi-plus-lg"></i> Add Answer</button>`; newQuestionHtml += `<button type="button" class="btn btn-sm btn-success add-answer shadow mt-2"><i class="bi bi-plus-lg"></i> Add Answer</button>`;
newQuestionHtml += `<button type="button" class="btn btn-sm btn-info add-other-option shadow mt-2 ml-2"><i class="bi bi-pencil-square"></i> Add "Other" Option</button>`;
newQuestionHtml += `<hr class="border m-2">` newQuestionHtml += `<hr class="border m-2">`
newQuestionHtml += `</div> `; newQuestionHtml += `</div> `;
newQuestionHtml += `</div>`; newQuestionHtml += `</div>`;
@ -132,15 +685,17 @@
questionIndex++; questionIndex++;
}); });
// Add regular answer
$("#questions-container").on("click", ".add-answer", function () { $("#questions-container").on("click", ".add-answer", function () {
var questionIndex = $(this).closest('.answers-container').data('question-index'); var questionIndex = $(this).closest('.answers-container').data('question-index');
var answerIndex = $(this).closest('.answers-container').find('.answer-group').length; var answerIndex = $(this).closest('.answers-container').find('.answer-group').length;
var answerGroupHtml = ` var answerGroupHtml = `
<div class="answer-group"> <div class="answer-group fade-in">
<br> <br>
<label class="control-label">Answer ${answerIndex + 1}</label> <label class="control-label">Answer ${answerIndex + 1}</label>
<input type="text" class="form-control" name="Questions[${questionIndex}].Answers[${answerIndex}].Text" placeholder="new answer"/> <input type="text" class="form-control" name="Questions[${questionIndex}].Answers[${answerIndex}].Text" placeholder="new answer"/>
<input type="hidden" name="Questions[${questionIndex}].Answers[${answerIndex}].IsOtherOption" value="false"/>
<button type="button" class="btn btn-danger btn-sm mt-1 remove-answer"><i class="bi bi-trash3-fill"></i></button> <button type="button" class="btn btn-danger btn-sm mt-1 remove-answer"><i class="bi bi-trash3-fill"></i></button>
<br> <br>
</div>`; </div>`;
@ -149,6 +704,34 @@
$(this).prev('.answer-group').find('.remove-answer').show(); $(this).prev('.answer-group').find('.remove-answer').show();
}); });
// Add "Other" option
$("#questions-container").on("click", ".add-other-option", function () {
var questionIndex = $(this).closest('.answers-container').data('question-index');
var answerIndex = $(this).closest('.answers-container').find('.answer-group').length;
// Check if "Other" option already exists
var existingOther = $(this).closest('.answers-container').find('.other-option-group');
if (existingOther.length > 0) {
alert('An "Other" option already exists for this question.');
return;
}
var otherOptionHtml = `
<div class="answer-group other-option-group fade-in">
<br>
<label class="control-label"><strong>Other Option ${answerIndex + 1}</strong></label>
<div class="alert alert-info" role="alert">
<small><i class="bi bi-info-circle"></i> This will allow users to write their own answer</small>
</div>
<input type="text" class="form-control" name="Questions[${questionIndex}].Answers[${answerIndex}].Text" placeholder="Other (please specify)" value="Other (please specify)"/>
<input type="hidden" name="Questions[${questionIndex}].Answers[${answerIndex}].IsOtherOption" value="true"/>
<button type="button" class="btn btn-warning btn-sm mt-1 remove-answer"><i class="bi bi-trash3-fill"></i> Remove Other Option</button>
<br>
</div>`;
$(this).before(otherOptionHtml);
});
// Remove answer dynamically // Remove answer dynamically
$("#questions-container").on("click", ".remove-answer", function () { $("#questions-container").on("click", ".remove-answer", function () {
$(this).closest('.answer-group').remove(); $(this).closest('.answer-group').remove();
@ -160,14 +743,13 @@
}); });
// Save question // Save question
$("#questions-container").on("click", ".save-question", function () { $("#questions-container").on("click", ".save-question", function () {
var questionGroup = $(this).closest('.question-group'); var questionGroup = $(this).closest('.question-group');
questionGroup.find('.edit-question').show(); questionGroup.find('.edit-question').show();
questionGroup.find('.remove-question').hide(); questionGroup.find('.remove-question').hide();
questionGroup.find('.save-question').hide(); questionGroup.find('.save-question').hide();
questionGroup.find('.form-control').attr('disabled', true); // Disable question text and answer input fields questionGroup.find('.form-control').attr('disabled', true);
questionGroup.find('.question-type').attr('disabled', true); // Disable question type select field questionGroup.find('.question-type').attr('disabled', true);
questionGroup.find('.answers-container').slideUp(); questionGroup.find('.answers-container').slideUp();
questionGroup.addClass('collapsed'); questionGroup.addClass('collapsed');
@ -182,8 +764,8 @@
questionGroup.find('.save-question').show(); questionGroup.find('.save-question').show();
questionGroup.find('.remove-question').show(); questionGroup.find('.remove-question').show();
questionGroup.find('.edit-question').hide(); questionGroup.find('.edit-question').hide();
questionGroup.find('.form-control').attr('disabled', false); // Enable question text and answer input fields questionGroup.find('.form-control').attr('disabled', false);
questionGroup.find('.question-type').attr('disabled', false); // Enable question type select field questionGroup.find('.question-type').attr('disabled', false);
questionGroup.find('.answers-container').slideDown(); questionGroup.find('.answers-container').slideDown();
questionGroup.removeClass('collapsed'); questionGroup.removeClass('collapsed');
@ -193,12 +775,10 @@
// Enable form fields before form submission // Enable form fields before form submission
$("form").submit(function () { $("form").submit(function () {
$(this).find('.form-control').prop('disabled', false); // Enable all form fields before submission $(this).find('.form-control').prop('disabled', false);
}); });
}); });
</script> </script>
} }

File diff suppressed because it is too large Load diff

View file

@ -98,6 +98,44 @@
border-radius: 4px; border-radius: 4px;
margin-bottom: 5px; margin-bottom: 5px;
} }
/* NEW: Styles for "Other" text responses */
.other-text-response {
background-color: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
padding: 10px 12px;
margin-top: 8px;
font-size: 0.9em;
}
.other-text-label {
font-weight: bold;
color: #856404;
margin-bottom: 5px;
display: flex;
align-items: center;
font-size: 0.85em;
}
.other-text-content {
color: #495057;
font-style: italic;
line-height: 1.3;
background-color: #fff;
padding: 6px 10px;
border-radius: 4px;
border-left: 3px solid #ffc107;
}
.other-option-badge {
background-color: #ffc107;
color: #212529;
font-size: 0.7em;
padding: 2px 6px;
border-radius: 3px;
margin-left: 5px;
}
</style> </style>
<div class="container-fluid mt-3"> <div class="container-fluid mt-3">
@ -244,11 +282,32 @@
<div class="d-flex flex-wrap gap-1"> <div class="d-flex flex-wrap gap-1">
@foreach (var answer in responseDetail.ResponseAnswers) @foreach (var answer in responseDetail.ResponseAnswers)
{ {
var answerText = question.Answers.FirstOrDefault(a => a.Id == answer.AnswerId)?.Text; var answerObj = question.Answers.FirstOrDefault(a => a.Id == answer.AnswerId);
<span class="badge bg-success">@answerText</span> var answerText = answerObj?.Text;
<span class="badge bg-success">
@answerText
@if (answerObj?.IsOtherOption == true)
{
<span class="other-option-badge">Other</span>
}
</span>
} }
</div> </div>
} }
@* NEW: Display "Other" text if available *@
@if (!string.IsNullOrEmpty(responseDetail.OtherText))
{
<div class="other-text-response">
<div class="other-text-label">
<i class="bi bi-pencil-square me-1"></i>
Custom "Other" Response:
</div>
<div class="other-text-content">
@responseDetail.OtherText
</div>
</div>
}
} }
else else
{ {
@ -311,10 +370,10 @@
var responsesData = [ var responsesData = [
@foreach (var response in Model.Responses) @foreach (var response in Model.Responses)
{ {
@foreach (var detail in response.ResponseDetails.Where(d => d.QuestionType == QuestionType.Slider)) @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))], @:['@response.Questionnaire.Title - @response.SubmissionDate.ToString("yyyy-MMMM-dd HH:mm")', getDescriptiveValue(@(detail.TextResponse != null ? int.Parse(detail.TextResponse) : 0))],
} }
} }
]; ];
@ -363,21 +422,21 @@
data.addColumn('number', 'Total Answers'); data.addColumn('number', 'Total Answers');
var userSurveySummaryData = @Html.Raw(JsonConvert.SerializeObject( var userSurveySummaryData = @Html.Raw(JsonConvert.SerializeObject(
Model.Responses Model.Responses
.GroupBy(response => response.UserEmail) .GroupBy(response => response.UserEmail)
.Select(g => new .Select(g => new
{ {
UserName = g.First().UserName, UserName = g.First().UserName,
TotalSurveys = g.Count(), TotalSurveys = g.Count(),
TotalQuestions = g.SelectMany(r => r.ResponseDetails).Count(), TotalQuestions = g.SelectMany(r => r.ResponseDetails).Count(),
TotalAnswers = g.SelectMany(r => r.ResponseDetails.SelectMany(rd => rd.ResponseAnswers)).Count() TotalAnswers = g.SelectMany(r => r.ResponseDetails.SelectMany(rd => rd.ResponseAnswers)).Count()
}) })
.ToList() .ToList()
)); ));
userSurveySummaryData.forEach(function (row) { userSurveySummaryData.forEach(function (row) {
data.addRow([row.UserName, row.TotalSurveys, row.TotalQuestions, row.TotalAnswers]); data.addRow([row.UserName, row.TotalSurveys, row.TotalQuestions, row.TotalAnswers]);
}); });
var options = { var options = {
title: 'User Survey Summary', title: 'User Survey Summary',
@ -395,45 +454,45 @@
} }
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
google.charts.load('current', { packages: ['corechart'] }); google.charts.load('current', { packages: ['corechart'] });
google.charts.setOnLoadCallback(drawSurveyQuestionsAnswersSummaryCharts); google.charts.setOnLoadCallback(drawSurveyQuestionsAnswersSummaryCharts);
function drawSurveyQuestionsAnswersSummaryCharts() { function drawSurveyQuestionsAnswersSummaryCharts() {
var surveyQuestionsSummaryData = @Html.Raw(JsonConvert.SerializeObject( var surveyQuestionsSummaryData = @Html.Raw(JsonConvert.SerializeObject(
Model.Responses Model.Responses
.GroupBy(response => response.Questionnaire.Title) .GroupBy(response => response.Questionnaire.Title)
.Select(g => new .Select(g => new
{ {
SurveyTitle = g.Key, SurveyTitle = g.Key,
SurveyId = g.First().Questionnaire.Id, SurveyId = g.First().Questionnaire.Id,
TotalQuestions = g.SelectMany(r => r.ResponseDetails).Count(), TotalQuestions = g.SelectMany(r => r.ResponseDetails).Count(),
TotalAnswers = g.SelectMany(r => r.ResponseDetails.SelectMany(rd => rd.ResponseAnswers)).Count() TotalAnswers = g.SelectMany(r => r.ResponseDetails.SelectMany(rd => rd.ResponseAnswers)).Count()
}) })
.ToList() .ToList()
)); ));
surveyQuestionsSummaryData.forEach(function (row) { surveyQuestionsSummaryData.forEach(function (row) {
drawSingleSurveyChart(row.SurveyTitle, row.SurveyId, row.TotalQuestions, row.TotalAnswers); drawSingleSurveyChart(row.SurveyTitle, row.SurveyId, row.TotalQuestions, row.TotalAnswers);
}); });
} }
function drawSingleSurveyChart(surveyTitle, surveyId, totalQuestions, totalAnswers) { function drawSingleSurveyChart(surveyTitle, surveyId, totalQuestions, totalAnswers) {
var data = new google.visualization.DataTable(); var data = new google.visualization.DataTable();
data.addColumn('string', 'Category'); data.addColumn('string', 'Category');
data.addColumn('number', 'Count'); data.addColumn('number', 'Count');
data.addRow(['Questions', totalQuestions]); data.addRow(['Questions', totalQuestions]);
data.addRow(['Answers', totalAnswers]); data.addRow(['Answers', totalAnswers]);
var options = { var options = {
is3D: true, is3D: true,
titleTextStyle: { color: '#17a2b8', fontSize: 16, bold: true }, titleTextStyle: { color: '#17a2b8', fontSize: 16, bold: true },
}; };
var chart = new google.visualization.PieChart(document.getElementById('survey_questions_answers_summary_chart_div_' + surveyId)); var chart = new google.visualization.PieChart(document.getElementById('survey_questions_answers_summary_chart_div_' + surveyId));
chart.draw(data, options); chart.draw(data, options);
} }
</script> </script>
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {

View file

@ -179,7 +179,6 @@ namespace Web.Controllers
} }
catch (System.Text.Json.JsonException ex) catch (System.Text.Json.JsonException ex)
{ {
// Log error if needed
Console.WriteLine($"Error parsing tracking data: {ex.Message}"); Console.WriteLine($"Error parsing tracking data: {ex.Message}");
} }
@ -209,7 +208,10 @@ namespace Web.Controllers
: null, : null,
ResponseAnswers = answeredQuestion.SelectedAnswerIds ResponseAnswers = answeredQuestion.SelectedAnswerIds
.Select(aid => new ResponseAnswer { AnswerId = aid }) .Select(aid => new ResponseAnswer { AnswerId = aid })
.ToList() .ToList(),
// ✅ NEW: Handle Other Text
OtherText = GetOtherTextForQuestion(answeredQuestion, dbQuestion)
}; };
} }
else if (skippedInfo != null) else if (skippedInfo != null)
@ -252,6 +254,34 @@ namespace Web.Controllers
return responseDetails; return responseDetails;
} }
// ✅ NEW METHOD: Extract other text for a question
private string GetOtherTextForQuestion(ResponseQuestionViewModel answeredQuestion, Question dbQuestion)
{
if (answeredQuestion.OtherTexts == null || !answeredQuestion.OtherTexts.Any())
return null;
var otherTexts = new List<string>();
// Loop through selected answers and check if any are "other" options
foreach (var selectedAnswerId in answeredQuestion.SelectedAnswerIds)
{
// Find the corresponding answer in the database
var dbAnswer = dbQuestion.Answers.FirstOrDefault(a => a.Id == selectedAnswerId);
// If this answer is an "other" option and has custom text
if (dbAnswer != null && dbAnswer.IsOtherOption && answeredQuestion.OtherTexts.ContainsKey(selectedAnswerId))
{
var customText = answeredQuestion.OtherTexts[selectedAnswerId];
if (!string.IsNullOrWhiteSpace(customText))
{
otherTexts.Add($"{dbAnswer.Text}: {customText}");
}
}
}
return otherTexts.Any() ? string.Join("; ", otherTexts) : null;
}
private int GetQuestionNumber(int questionId, List<Question> allQuestions) private int GetQuestionNumber(int questionId, List<Question> allQuestions)
{ {
return allQuestions.FindIndex(q => q.Id == questionId) + 1; return allQuestions.FindIndex(q => q.Id == questionId) + 1;
@ -265,7 +295,11 @@ namespace Web.Controllers
bool hasAnswerResponse = question.SelectedAnswerIds != null && bool hasAnswerResponse = question.SelectedAnswerIds != null &&
question.SelectedAnswerIds.Any(); question.SelectedAnswerIds.Any();
return hasTextResponse || hasAnswerResponse; // ✅ NEW: Check for other text responses
bool hasOtherTextResponse = question.OtherTexts != null &&
question.OtherTexts.Any(kv => !string.IsNullOrWhiteSpace(kv.Value));
return hasTextResponse || hasAnswerResponse || hasOtherTextResponse;
} }
// Add this class for JSON deserialization // Add this class for JSON deserialization
@ -438,7 +472,8 @@ namespace Web.Controllers
{ {
Id = a.Id, Id = a.Id,
Text = a.Text, Text = a.Text,
ConditionJson = a.ConditionJson // Add this line IsOtherOption = a.IsOtherOption, // ← ADD THIS LINE!
ConditionJson = a.ConditionJson
}).ToList() }).ToList()
}).ToList() }).ToList()
}; };

View file

@ -0,0 +1,844 @@
// <auto-generated />
using System;
using Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Web.Migrations
{
[DbContext(typeof(SurveyContext))]
[Migration("20250813151802_AddIsOtherOptionToAnswer")]
partial class AddIsOtherOptionToAnswer
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.4")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Model.Address", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("CVR")
.HasColumnType("nvarchar(max)");
b.Property<string>("City")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Country")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Mobile")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PostalCode")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("State")
.HasColumnType("nvarchar(max)");
b.Property<string>("Street")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Addresss");
});
modelBuilder.Entity("Model.Answer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ConditionJson")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsOtherOption")
.HasColumnType("bit");
b.Property<int>("QuestionId")
.HasColumnType("int");
b.Property<string>("Text")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.ToTable("Answers");
});
modelBuilder.Entity("Model.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FirstName")
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Model.Banner", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ImageUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("LinkUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Banners");
});
modelBuilder.Entity("Model.Footer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ImageUlr")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("LastUpdated")
.HasColumnType("datetime2");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Owner")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Sitecopyright")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("UpdatedBy")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Footers");
});
modelBuilder.Entity("Model.FooterSocialMedia", b =>
{
b.Property<int>("FooterId")
.HasColumnType("int");
b.Property<int>("SocialId")
.HasColumnType("int");
b.HasKey("FooterId", "SocialId");
b.HasIndex("SocialId");
b.ToTable("FooterSocialMedias");
});
modelBuilder.Entity("Model.Page", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BannerId")
.HasColumnType("int");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("FooterId")
.HasColumnType("int");
b.Property<string>("Slug")
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("BannerId");
b.HasIndex("FooterId");
b.ToTable("Pages");
});
modelBuilder.Entity("Model.Question", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("QuestionnaireId")
.HasColumnType("int");
b.Property<string>("Text")
.HasColumnType("nvarchar(max)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("QuestionnaireId");
b.ToTable("Questions");
});
modelBuilder.Entity("Model.Questionnaire", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Questionnaires");
});
modelBuilder.Entity("Model.Response", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("QuestionnaireId")
.HasColumnType("int");
b.Property<DateTime>("SubmissionDate")
.HasColumnType("datetime2");
b.Property<string>("UserEmail")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserName")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionnaireId");
b.ToTable("Responses");
});
modelBuilder.Entity("Model.ResponseAnswer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("AnswerId")
.HasColumnType("int");
b.Property<int>("ResponseDetailId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ResponseDetailId");
b.ToTable("ResponseAnswers");
});
modelBuilder.Entity("Model.ResponseDetail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("QuestionId")
.HasColumnType("int");
b.Property<int>("QuestionType")
.HasColumnType("int");
b.Property<int>("ResponseId")
.HasColumnType("int");
b.Property<string>("SkipReason")
.HasColumnType("nvarchar(max)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("TextResponse")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.HasIndex("ResponseId");
b.ToTable("ResponseDetails");
});
modelBuilder.Entity("Model.SentNewsletterEamil", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Body")
.HasColumnType("nvarchar(max)");
b.Property<string>("Geo")
.HasColumnType("nvarchar(max)");
b.Property<string>("IpAddress")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsBlocked")
.HasColumnType("bit");
b.Property<bool>("IsBounced")
.HasColumnType("bit");
b.Property<bool>("IsClicked")
.HasColumnType("bit");
b.Property<bool>("IsDelivered")
.HasColumnType("bit");
b.Property<bool>("IsOpened")
.HasColumnType("bit");
b.Property<bool>("IsSent")
.HasColumnType("bit");
b.Property<bool>("IsSpam")
.HasColumnType("bit");
b.Property<bool>("IsUnsubscribed")
.HasColumnType("bit");
b.Property<DateTime>("ReceivedActivity")
.HasColumnType("datetime2");
b.Property<string>("RecipientEmail")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("SentDate")
.HasColumnType("datetime2");
b.Property<string>("Subject")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("SentNewsletterEamils");
});
modelBuilder.Entity("Model.SocialMedia", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("SocialMedia");
});
modelBuilder.Entity("Model.Subscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsSubscribed")
.HasColumnType("bit");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Model.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Model.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Model.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Model.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Model.Answer", b =>
{
b.HasOne("Model.Question", "Question")
.WithMany("Answers")
.HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Question");
});
modelBuilder.Entity("Model.FooterSocialMedia", b =>
{
b.HasOne("Model.Footer", "Footer")
.WithMany("FooterSocialMedias")
.HasForeignKey("FooterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Model.SocialMedia", "SocialMedia")
.WithMany("FooterSocialMedias")
.HasForeignKey("SocialId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Footer");
b.Navigation("SocialMedia");
});
modelBuilder.Entity("Model.Page", b =>
{
b.HasOne("Model.Banner", "banner")
.WithMany()
.HasForeignKey("BannerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Model.Footer", "footer")
.WithMany()
.HasForeignKey("FooterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("banner");
b.Navigation("footer");
});
modelBuilder.Entity("Model.Question", b =>
{
b.HasOne("Model.Questionnaire", "Questionnaire")
.WithMany("Questions")
.HasForeignKey("QuestionnaireId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Questionnaire");
});
modelBuilder.Entity("Model.Response", b =>
{
b.HasOne("Model.Questionnaire", "Questionnaire")
.WithMany()
.HasForeignKey("QuestionnaireId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Questionnaire");
});
modelBuilder.Entity("Model.ResponseAnswer", b =>
{
b.HasOne("Model.ResponseDetail", "ResponseDetail")
.WithMany("ResponseAnswers")
.HasForeignKey("ResponseDetailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ResponseDetail");
});
modelBuilder.Entity("Model.ResponseDetail", b =>
{
b.HasOne("Model.Question", "Question")
.WithMany()
.HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Model.Response", "Response")
.WithMany("ResponseDetails")
.HasForeignKey("ResponseId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Question");
b.Navigation("Response");
});
modelBuilder.Entity("Model.Footer", b =>
{
b.Navigation("FooterSocialMedias");
});
modelBuilder.Entity("Model.Question", b =>
{
b.Navigation("Answers");
});
modelBuilder.Entity("Model.Questionnaire", b =>
{
b.Navigation("Questions");
});
modelBuilder.Entity("Model.Response", b =>
{
b.Navigation("ResponseDetails");
});
modelBuilder.Entity("Model.ResponseDetail", b =>
{
b.Navigation("ResponseAnswers");
});
modelBuilder.Entity("Model.SocialMedia", b =>
{
b.Navigation("FooterSocialMedias");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Web.Migrations
{
/// <inheritdoc />
public partial class AddIsOtherOptionToAnswer : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsOtherOption",
table: "Answers",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsOtherOption",
table: "Answers");
}
}
}

View file

@ -0,0 +1,847 @@
// <auto-generated />
using System;
using Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Web.Migrations
{
[DbContext(typeof(SurveyContext))]
[Migration("20250813162055_AddOtherTextToResponseDetail")]
partial class AddOtherTextToResponseDetail
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.4")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Model.Address", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("CVR")
.HasColumnType("nvarchar(max)");
b.Property<string>("City")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Country")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Mobile")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("PostalCode")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("State")
.HasColumnType("nvarchar(max)");
b.Property<string>("Street")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Addresss");
});
modelBuilder.Entity("Model.Answer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ConditionJson")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsOtherOption")
.HasColumnType("bit");
b.Property<int>("QuestionId")
.HasColumnType("int");
b.Property<string>("Text")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.ToTable("Answers");
});
modelBuilder.Entity("Model.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<string>("FirstName")
.HasColumnType("nvarchar(max)");
b.Property<string>("LastName")
.HasColumnType("nvarchar(max)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Model.Banner", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ImageUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("LinkUrl")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Banners");
});
modelBuilder.Entity("Model.Footer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Content")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ImageUlr")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("LastUpdated")
.HasColumnType("datetime2");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Owner")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Sitecopyright")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("UpdatedBy")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Footers");
});
modelBuilder.Entity("Model.FooterSocialMedia", b =>
{
b.Property<int>("FooterId")
.HasColumnType("int");
b.Property<int>("SocialId")
.HasColumnType("int");
b.HasKey("FooterId", "SocialId");
b.HasIndex("SocialId");
b.ToTable("FooterSocialMedias");
});
modelBuilder.Entity("Model.Page", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("BannerId")
.HasColumnType("int");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("FooterId")
.HasColumnType("int");
b.Property<string>("Slug")
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("BannerId");
b.HasIndex("FooterId");
b.ToTable("Pages");
});
modelBuilder.Entity("Model.Question", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("QuestionnaireId")
.HasColumnType("int");
b.Property<string>("Text")
.HasColumnType("nvarchar(max)");
b.Property<int>("Type")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("QuestionnaireId");
b.ToTable("Questions");
});
modelBuilder.Entity("Model.Questionnaire", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Description")
.HasColumnType("nvarchar(max)");
b.Property<string>("Title")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Questionnaires");
});
modelBuilder.Entity("Model.Response", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("QuestionnaireId")
.HasColumnType("int");
b.Property<DateTime>("SubmissionDate")
.HasColumnType("datetime2");
b.Property<string>("UserEmail")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserName")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionnaireId");
b.ToTable("Responses");
});
modelBuilder.Entity("Model.ResponseAnswer", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("AnswerId")
.HasColumnType("int");
b.Property<int>("ResponseDetailId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ResponseDetailId");
b.ToTable("ResponseAnswers");
});
modelBuilder.Entity("Model.ResponseDetail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("OtherText")
.HasColumnType("nvarchar(max)");
b.Property<int>("QuestionId")
.HasColumnType("int");
b.Property<int>("QuestionType")
.HasColumnType("int");
b.Property<int>("ResponseId")
.HasColumnType("int");
b.Property<string>("SkipReason")
.HasColumnType("nvarchar(max)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<string>("TextResponse")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("QuestionId");
b.HasIndex("ResponseId");
b.ToTable("ResponseDetails");
});
modelBuilder.Entity("Model.SentNewsletterEamil", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Body")
.HasColumnType("nvarchar(max)");
b.Property<string>("Geo")
.HasColumnType("nvarchar(max)");
b.Property<string>("IpAddress")
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsBlocked")
.HasColumnType("bit");
b.Property<bool>("IsBounced")
.HasColumnType("bit");
b.Property<bool>("IsClicked")
.HasColumnType("bit");
b.Property<bool>("IsDelivered")
.HasColumnType("bit");
b.Property<bool>("IsOpened")
.HasColumnType("bit");
b.Property<bool>("IsSent")
.HasColumnType("bit");
b.Property<bool>("IsSpam")
.HasColumnType("bit");
b.Property<bool>("IsUnsubscribed")
.HasColumnType("bit");
b.Property<DateTime>("ReceivedActivity")
.HasColumnType("datetime2");
b.Property<string>("RecipientEmail")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("SentDate")
.HasColumnType("datetime2");
b.Property<string>("Subject")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("SentNewsletterEamils");
});
modelBuilder.Entity("Model.SocialMedia", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Url")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("SocialMedia");
});
modelBuilder.Entity("Model.Subscription", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Email")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsSubscribed")
.HasColumnType("bit");
b.Property<string>("Name")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Subscriptions");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Model.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Model.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Model.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Model.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Model.Answer", b =>
{
b.HasOne("Model.Question", "Question")
.WithMany("Answers")
.HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Question");
});
modelBuilder.Entity("Model.FooterSocialMedia", b =>
{
b.HasOne("Model.Footer", "Footer")
.WithMany("FooterSocialMedias")
.HasForeignKey("FooterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Model.SocialMedia", "SocialMedia")
.WithMany("FooterSocialMedias")
.HasForeignKey("SocialId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Footer");
b.Navigation("SocialMedia");
});
modelBuilder.Entity("Model.Page", b =>
{
b.HasOne("Model.Banner", "banner")
.WithMany()
.HasForeignKey("BannerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Model.Footer", "footer")
.WithMany()
.HasForeignKey("FooterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("banner");
b.Navigation("footer");
});
modelBuilder.Entity("Model.Question", b =>
{
b.HasOne("Model.Questionnaire", "Questionnaire")
.WithMany("Questions")
.HasForeignKey("QuestionnaireId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Questionnaire");
});
modelBuilder.Entity("Model.Response", b =>
{
b.HasOne("Model.Questionnaire", "Questionnaire")
.WithMany()
.HasForeignKey("QuestionnaireId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Questionnaire");
});
modelBuilder.Entity("Model.ResponseAnswer", b =>
{
b.HasOne("Model.ResponseDetail", "ResponseDetail")
.WithMany("ResponseAnswers")
.HasForeignKey("ResponseDetailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ResponseDetail");
});
modelBuilder.Entity("Model.ResponseDetail", b =>
{
b.HasOne("Model.Question", "Question")
.WithMany()
.HasForeignKey("QuestionId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Model.Response", "Response")
.WithMany("ResponseDetails")
.HasForeignKey("ResponseId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Question");
b.Navigation("Response");
});
modelBuilder.Entity("Model.Footer", b =>
{
b.Navigation("FooterSocialMedias");
});
modelBuilder.Entity("Model.Question", b =>
{
b.Navigation("Answers");
});
modelBuilder.Entity("Model.Questionnaire", b =>
{
b.Navigation("Questions");
});
modelBuilder.Entity("Model.Response", b =>
{
b.Navigation("ResponseDetails");
});
modelBuilder.Entity("Model.ResponseDetail", b =>
{
b.Navigation("ResponseAnswers");
});
modelBuilder.Entity("Model.SocialMedia", b =>
{
b.Navigation("FooterSocialMedias");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Web.Migrations
{
/// <inheritdoc />
public partial class AddOtherTextToResponseDetail : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "OtherText",
table: "ResponseDetails",
type: "nvarchar(max)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "OtherText",
table: "ResponseDetails");
}
}
}

View file

@ -209,6 +209,9 @@ namespace Web.Migrations
b.Property<string>("ConditionJson") b.Property<string>("ConditionJson")
.HasColumnType("nvarchar(max)"); .HasColumnType("nvarchar(max)");
b.Property<bool>("IsOtherOption")
.HasColumnType("bit");
b.Property<int>("QuestionId") b.Property<int>("QuestionId")
.HasColumnType("int"); .HasColumnType("int");
@ -522,6 +525,9 @@ namespace Web.Migrations
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id")); SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("OtherText")
.HasColumnType("nvarchar(max)");
b.Property<int>("QuestionId") b.Property<int>("QuestionId")
.HasColumnType("int"); .HasColumnType("int");

View file

@ -6,6 +6,7 @@
public string? Text { get; set; } // Answer text public string? Text { get; set; } // Answer text
public int? Count { get; set; } public int? Count { get; set; }
public bool IsOtherOption { get; set; } = false;
public string? ConditionJson { get; set; } // Add this line for conditional logic public string? ConditionJson { get; set; } // Add this line for conditional logic
} }
} }

View file

@ -16,5 +16,7 @@ namespace Web.ViewModel.QuestionnaireVM
public List<int> SelectedAnswerIds { get; set; } = new List<int>(); public List<int> SelectedAnswerIds { get; set; } = new List<int>();
public List<string> SelectedText { get; set; } = new List<string>(); public List<string> SelectedText { get; set; } = new List<string>();
public Dictionary<int, string> OtherTexts { get; set; } = new Dictionary<int, string>();
} }
} }

View file

@ -192,6 +192,59 @@
opacity: 0.8; opacity: 0.8;
} }
/* Other Option Styling */
.other-option-container {
position: relative;
background: linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(217, 119, 6, 0.05) 100%);
border: 1px solid rgba(245, 158, 11, 0.2);
border-radius: 16px;
padding: 16px;
margin-bottom: 16px;
}
.other-option-badge {
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
color: white;
padding: 4px 8px;
border-radius: 8px;
font-size: 11px;
font-weight: 600;
margin-left: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.other-text-container {
margin-top: 12px;
padding: 12px;
background: var(--surface-secondary);
border-radius: 12px;
border: 1px solid var(--border-accent);
animation: morphIn 0.3s ease;
display: none !important; /* Initially hidden */
}
.other-text-container.show {
display: block !important;
}
.other-text-input {
background: var(--surface-glass);
border: 1px solid var(--border-subtle);
color: var(--text-primary);
border-radius: 12px;
padding: 12px 16px;
font-size: 14px;
resize: vertical;
min-height: 80px;
}
.other-text-input:focus {
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px rgba(74, 144, 164, 0.15);
background: rgba(26, 42, 64, 0.8);
}
/* Next-Gen Buttons */ /* Next-Gen Buttons */
.btn { .btn {
border-radius: 16px; border-radius: 16px;
@ -1011,9 +1064,34 @@
<div class="form-group"> <div class="form-group">
@foreach (var answer in question.Answers) @foreach (var answer in question.Answers)
{ {
<div class="form-check mb-2"> <div class="form-check mb-2 @(answer.IsOtherOption ? "other-option-container" : "")">
<input class="form-check-input" id="question@(i)_answer@(answer.Id)" type="checkbox" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" data-condition="@answer.ConditionJson"> <input class="form-check-input @(answer.IsOtherOption ? "other-option-input" : "")"
<label class="form-check-label" for="question@(i)_answer@(answer.Id)">@answer.Text</label> id="question@(i)_answer@(answer.Id)"
type="checkbox"
name="Questions[@i].SelectedAnswerIds"
value="@answer.Id"
data-condition="@answer.ConditionJson"
data-question-index="@i"
data-answer-id="@answer.Id"
data-is-other="@answer.IsOtherOption.ToString().ToLower()">
<label class="form-check-label" for="question@(i)_answer@(answer.Id)">
@answer.Text
@if (answer.IsOtherOption)
{
<span class="other-option-badge">Other Option</span>
}
</label>
@if (answer.IsOtherOption)
{
<div class="other-text-container" id="otherText_@(i)_@(answer.Id)" style="display: none;">
<textarea class="form-control other-text-input"
name="Questions[@i].OtherTexts[@answer.Id]"
id="otherTextArea_@(i)_@(answer.Id)"
rows="3"
placeholder="Please specify..."></textarea>
</div>
}
</div> </div>
} }
</div> </div>
@ -1023,9 +1101,34 @@
<div class="form-check"> <div class="form-check">
@foreach (var answer in question.Answers) @foreach (var answer in question.Answers)
{ {
<div class="form-check mb-2"> <div class="form-check mb-2 @(answer.IsOtherOption ? "other-option-container" : "")">
<input class="form-check-input answer-input" type="radio" id="question@(i)_answer@(answer.Id)" name="Questions[@i].SelectedAnswerIds" value="@answer.Id" data-condition="@answer.ConditionJson"> <input class="form-check-input answer-input @(answer.IsOtherOption ? "other-option-input" : "")"
<label class="form-check-label" for="question@(i)_answer@(answer.Id)">@answer.Text</label> type="radio"
id="question@(i)_answer@(answer.Id)"
name="Questions[@i].SelectedAnswerIds"
value="@answer.Id"
data-condition="@answer.ConditionJson"
data-question-index="@i"
data-answer-id="@answer.Id"
data-is-other="@answer.IsOtherOption.ToString().ToLower()">
<label class="form-check-label" for="question@(i)_answer@(answer.Id)">
@answer.Text
@if (answer.IsOtherOption)
{
<span class="other-option-badge">Other Option</span>
}
</label>
@if (answer.IsOtherOption)
{
<div class="other-text-container" id="otherText_@(i)_@(answer.Id)" style="display: none;">
<textarea class="form-control other-text-input"
name="Questions[@i].OtherTexts[@answer.Id]"
id="otherTextArea_@(i)_@(answer.Id)"
rows="3"
placeholder="Please specify..."></textarea>
</div>
}
</div> </div>
} }
</div> </div>
@ -1232,6 +1335,81 @@
const form = document.getElementById('questionnaireForm'); const form = document.getElementById('questionnaireForm');
if (!form) { console.error('Form not found!'); return; } if (!form) { console.error('Form not found!'); return; }
// ===== OTHER OPTION FUNCTIONALITY =====
// Handle showing/hiding text areas for "Other" options
function initializeOtherOptions() {
console.log('Initializing other options...');
// Handle checkbox other options
$(document).on('change', 'input[type="checkbox"]', function() {
console.log('Checkbox changed:', this);
const $checkbox = $(this);
const isOther = $checkbox.attr('data-is-other') === 'true';
if (isOther) {
console.log('Other checkbox detected');
const questionIndex = $checkbox.attr('data-question-index');
const answerId = $checkbox.attr('data-answer-id');
const $textContainer = $(`#otherText_${questionIndex}_${answerId}`);
const $textArea = $(`#otherTextArea_${questionIndex}_${answerId}`);
console.log('Looking for container:', `#otherText_${questionIndex}_${answerId}`);
console.log('Container found:', $textContainer.length);
if ($checkbox.is(':checked')) {
console.log('Showing text container');
$textContainer.addClass('show').slideDown(300);
$textArea.focus();
$textArea.prop('required', true);
} else {
console.log('Hiding text container');
$textContainer.removeClass('show').slideUp(300);
$textArea.val('');
$textArea.prop('required', false);
}
}
});
// Handle radio button other options
$(document).on('change', 'input[type="radio"]', function() {
console.log('Radio button changed:', this);
const $radio = $(this);
const questionIndex = $radio.attr('data-question-index');
const isOther = $radio.attr('data-is-other') === 'true';
// First, hide all other text containers for this question
if (questionIndex) {
console.log('Hiding all other containers for question:', questionIndex);
$(`.other-text-container[id^="otherText_${questionIndex}_"]`).removeClass('show').slideUp(300);
$(`.other-text-input[id^="otherTextArea_${questionIndex}_"]`).prop('required', false).val('');
}
// If this is an other option and it's selected, show its text container
if (isOther && $radio.is(':checked')) {
console.log('Other radio selected');
const answerId = $radio.attr('data-answer-id');
const $textContainer = $(`#otherText_${questionIndex}_${answerId}`);
const $textArea = $(`#otherTextArea_${questionIndex}_${answerId}`);
console.log('Showing text container for radio');
$textContainer.addClass('show').slideDown(300);
$textArea.focus();
$textArea.prop('required', true);
}
});
// Debug: List all other option inputs
console.log('Other option inputs found:', $('input[data-is-other="true"]').length);
$('input[data-is-other="true"]').each(function() {
console.log('Other input:', this, 'Question:', $(this).attr('data-question-index'), 'Answer:', $(this).attr('data-answer-id'));
});
}
// Initialize other options functionality when DOM is ready
$(document).ready(function() {
initializeOtherOptions();
});
// Tracking // Tracking
let questionsShownArray = [1]; let questionsShownArray = [1];
let questionsSkippedArray = []; let questionsSkippedArray = [];