Improve overall questionnaire design and user response experience

This commit is contained in:
Qaisyousuf 2025-08-15 17:26:46 +02:00
parent d9f9b600d7
commit b67eca0729
12 changed files with 6963 additions and 1325 deletions

View file

@ -23,5 +23,7 @@ namespace Model
public bool IsOtherOption { get; set; } = false; public bool IsOtherOption { get; set; } = false;
public string? ConditionJson { get; set; } public string? ConditionJson { get; set; }
[NotMapped] // This won't be stored in database
public bool IsDeleted { get; set; } = false;
} }
} }

View file

@ -47,7 +47,10 @@ namespace Services.Implemnetation
public Questionnaire GetQuestionnaireWithQuestionAndAnswer(int? id) public Questionnaire GetQuestionnaireWithQuestionAndAnswer(int? id)
{ {
return _context.Questionnaires.AsNoTracking().Include(x => x.Questions).ThenInclude(x => x.Answers).FirstOrDefault(x=>x.Id==id); return _context.Questionnaires // ✅ No AsNoTracking for edit operations!
.Include(x => x.Questions)
.ThenInclude(x => x.Answers)
.FirstOrDefault(x => x.Id == id);
} }
public async Task Update(Questionnaire questionnaire) public async Task Update(Questionnaire questionnaire)

View file

@ -200,110 +200,130 @@ namespace Web.Areas.Admin.Controllers
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
// Retrieve the existing questionnaire from the database try
var existingQuestionnaire = _questionnaire.GetQuestionnaireWithQuestionAndAnswer(viewModel.Id);
if (existingQuestionnaire == null)
{ {
return NotFound(); using var transaction = await _context.Database.BeginTransactionAsync();
}
// Update the existing questionnaire with the data from the view model try
existingQuestionnaire.Title = viewModel.Title;
existingQuestionnaire.Description = viewModel.Description;
var existingQuestionIds = existingQuestionnaire.Questions.Select(q => q.Id).ToList();
// Iterate through existing questions and remove those not found in the view model
foreach (var existingQuestion in existingQuestionnaire.Questions.ToList())
{
if (!viewModel.Questions.Any(q => q.Id == existingQuestion.Id))
{ {
existingQuestionnaire.Questions.Remove(existingQuestion); // Step 1: Update the questionnaire basic info
} var existingQuestionnaire = await _context.Questionnaires
await _questionnaire.Update(existingQuestionnaire); .FirstOrDefaultAsync(q => q.Id == viewModel.Id);
}
var newQuestions = new List<Question>(); if (existingQuestionnaire == null)
// Update or add new questions
foreach (var questionViewModel in viewModel.Questions)
{
var existingQuestion = existingQuestionnaire.Questions.FirstOrDefault(q => q.Id == questionViewModel.Id);
if (questionViewModel.Id != 0)
{
if (existingQuestion != null)
{ {
existingQuestion.Text = questionViewModel.Text; return NotFound();
existingQuestion.Type = questionViewModel.Type; }
foreach (var answerViewModel in questionViewModel.Answers) existingQuestionnaire.Title = viewModel.Title;
existingQuestionnaire.Description = viewModel.Description;
// Step 2: Get all existing questions for this questionnaire
var existingQuestions = await _context.Questions
.Where(q => q.QuestionnaireId == viewModel.Id)
.ToListAsync();
// Step 3: Delete ALL answers first (foreign key constraint)
if (existingQuestions.Any())
{
var questionIds = existingQuestions.Select(q => q.Id).ToList();
var existingAnswers = await _context.Answers
.Where(a => questionIds.Contains(a.QuestionId))
.ToListAsync();
_context.Answers.RemoveRange(existingAnswers);
await _context.SaveChangesAsync();
}
// Step 4: Delete ALL questions
_context.Questions.RemoveRange(existingQuestions);
await _context.SaveChangesAsync();
// Step 5: Add new questions (only if provided and valid)
int newQuestionsAdded = 0;
if (viewModel.Questions != null && viewModel.Questions.Count > 0)
{
var validQuestions = viewModel.Questions
.Where(q => !string.IsNullOrWhiteSpace(q.Text))
.ToList();
foreach (var questionViewModel in validQuestions)
{ {
// Check if the answer already exists var newQuestion = new Question
var existingAnswer = existingQuestion.Answers.FirstOrDefault(a => a.Id == answerViewModel.Id);
if (answerViewModel.Id == 0)
{ {
// NEW: Add IsOtherOption property when creating new answers Text = questionViewModel.Text.Trim(),
existingQuestion.Answers.Add(new Answer Type = questionViewModel.Type,
QuestionnaireId = viewModel.Id
};
_context.Questions.Add(newQuestion);
await _context.SaveChangesAsync(); // Save to get the ID
// Add answers for this question
if (questionViewModel.Answers != null)
{
var validAnswers = questionViewModel.Answers
.Where(a => !string.IsNullOrWhiteSpace(a.Text))
.ToList();
foreach (var answerViewModel in validAnswers)
{ {
Text = answerViewModel.Text, var newAnswer = new Answer
IsOtherOption = answerViewModel.IsOtherOption {
}); Text = answerViewModel.Text.Trim(),
} IsOtherOption = answerViewModel.IsOtherOption,
else if (answerViewModel.Text == null) QuestionId = newQuestion.Id
{ };
existingQuestion.Answers.Remove(existingAnswer);
await _questionnaire.Update(existingQuestionnaire); _context.Answers.Add(newAnswer);
} }
else if (existingAnswer != null)
{ if (validAnswers.Any())
existingAnswer.Text = answerViewModel.Text; {
// NEW: Update IsOtherOption property for existing answers await _context.SaveChangesAsync();
existingAnswer.IsOtherOption = answerViewModel.IsOtherOption; }
} }
newQuestionsAdded++;
} }
} }
// Step 6: Final save and commit
await _context.SaveChangesAsync();
await transaction.CommitAsync();
// Step 7: Get final count for success message
var finalQuestionCount = await _context.Questions
.Where(q => q.QuestionnaireId == viewModel.Id)
.CountAsync();
// Success message
if (finalQuestionCount == 0)
{
TempData["Success"] = "Questionnaire updated successfully. All questions have been removed.";
}
else
{
TempData["Success"] = $"Questionnaire updated successfully with {finalQuestionCount} question(s).";
}
return RedirectToAction(nameof(Index));
} }
else catch (Exception)
{ {
// Create a new question await transaction.RollbackAsync();
var newQuestion = new Question throw;
{
Text = questionViewModel.Text,
Type = questionViewModel.Type,
Answers = new List<Answer>()
};
foreach (var answerViewModel in questionViewModel.Answers)
{
if (!string.IsNullOrEmpty(answerViewModel.Text))
{
// NEW: Add IsOtherOption property when creating new answers
newQuestion.Answers.Add(new Answer
{
Text = answerViewModel.Text,
IsOtherOption = answerViewModel.IsOtherOption
});
}
}
newQuestions.Add(newQuestion);
} }
existingQuestionnaire.Questions.AddRange(newQuestions);
} }
catch (Exception ex)
await _questionnaire.Update(existingQuestionnaire); {
ModelState.AddModelError("", "An error occurred while updating the questionnaire. Please try again.");
TempData["Success"] = "Questionnaire updated successfully"; return View(viewModel);
return RedirectToAction(nameof(Index)); }
} }
return View(viewModel); return View(viewModel);
} }
[HttpGet] [HttpGet]
public IActionResult Delete(int id) public IActionResult Delete(int id)
{ {

View file

@ -1,177 +1,749 @@
@model QuestionnaireViewModel @model QuestionnaireViewModel
@{ @{
ViewData["Title"] = "Delete"; ViewData["Title"] = "Delete Questionnaire";
} }
<div class="container mt-4">
<div class="card justify-content-center p-4 shadow rounded">
<div class="card-body">
<h5 class="card-title">Delete questionnaire</h5>
@* <h6 class="text-danger">Are you sure you want to delete this questionnaire: <span class="badge bg-danger p-2 shadow rounded">@Model.Title</span></h6> *@
<h6 class="text-danger">
Are you sure you want to delete:
<span class="badge bg-danger p-2 shadow rounded"> <style>
<span class="item-title ">@Html.Raw(Model.Title.Length >= 100 ? Model.Title.Substring(0, 100) : Model.Title)</span> /* Modern Design System */
<span class="more-title " style="display:none;">@(Model.Title.Length > 30 ? Model.Title.Substring(30) : "")</span> @@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
</span>
<button type="button" class="btn btn-link readMoreBtn">Read More</button>
</h6> :root {
--danger-color: #ef4444;
--danger-light: #fca5a5;
--danger-dark: #dc2626;
--warning-color: #f59e0b;
--info-color: #06b6d4;
--success-color: #10b981;
--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: 24px;
}
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
<div class="row">
<!-- 12 columns for textboxes -->
<form asp-action="Delete" asp-controller="Questionnaire"> .container {
<div asp-validation-summary="All" class="text-danger"></div> max-width: 1000px;
}
<div class="mb-3 col-12"> /* Main Delete Card */
<label asp-for="Title" class="control-label"></label> .delete-card {
<input asp-for="Title" class="form-control" disabled /> background: white;
<span asp-validation-for="Title" class="text-danger"></span> border-radius: var(--border-radius-xl);
</div> box-shadow: var(--shadow-2xl);
<div class="mb-3 col-12"> border: 1px solid var(--gray-200);
<label asp-for="Description" class="control-label"></label> overflow: hidden;
<textarea asp-for="Description" class="form-control" disabled></textarea> position: relative;
<span asp-validation-for="Description" class="text-danger"></span> }
</div>
<div class="mb-3 col-12">
<span class="h5">Total Number of questions:<span class="badge-info p-2 shadow rounded"> @Model.Questions.Count</span></span><br />
@* <span class="h5">Total Number of answer: @Model.Questions.Select(x=>x.Answers.Select(x=>x.Id)).Count()</span> *@
</div>
<!-- Add the delete confirmation modal trigger button --> .delete-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(90deg, var(--danger-color), var(--danger-light), var(--danger-dark));
}
/* Header Section */
.delete-header {
background: linear-gradient(135deg, var(--danger-color) 0%, var(--danger-dark) 100%);
color: white;
padding: 3rem 2rem;
text-align: center;
position: relative;
overflow: hidden;
}
<div class="mb-3 container"> .delete-header::before {
<hr class="border border-primary border-2 opacity-50"> content: '';
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#deleteModal">Delete</button> position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 20"><defs><radialGradient id="a" cx="50%" cy="0%" r="100%"><stop offset="0%" stop-color="white" stop-opacity="0.1"/><stop offset="100%" stop-color="white" stop-opacity="0"/></radialGradient></defs><rect width="100" height="20" fill="url(%23a)"/></svg>');
opacity: 0.3;
}
<button asp-action="Index" class="btn btn-info">Back to list <i class="bi bi-arrow-return-left"></i></button> .delete-icon {
</div> background: rgba(255, 255, 255, 0.2);
</form> width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.5rem;
position: relative;
z-index: 1;
}
.delete-icon i {
font-size: 2.5rem;
color: white;
}
.delete-title {
font-size: 2.5rem;
font-weight: 800;
margin: 0 0 0.5rem;
text-shadow: 0 2px 4px rgba(0,0,0,0.1);
position: relative;
z-index: 1;
}
.delete-subtitle {
font-size: 1.1rem;
opacity: 0.9;
font-weight: 500;
position: relative;
z-index: 1;
}
/* Content Section */
.delete-content {
padding: 3rem 2rem;
}
/* Warning Alert */
.warning-alert {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border: 2px solid var(--warning-color);
border-radius: var(--border-radius-lg);
padding: 2rem;
margin-bottom: 2.5rem;
position: relative;
}
.warning-alert::before {
content: "⚠️";
position: absolute;
top: -15px;
left: 20px;
background: var(--warning-color);
color: white;
padding: 0.5rem 1rem;
border-radius: var(--border-radius-md);
font-size: 1.2rem;
font-weight: 700;
}
.warning-content {
margin-top: 0.5rem;
}
.warning-title {
font-size: 1.25rem;
font-weight: 700;
color: var(--gray-800);
margin-bottom: 1rem;
}
.warning-list {
list-style: none;
padding: 0;
margin: 0;
}
.warning-list li {
display: flex;
align-items: center;
margin-bottom: 0.75rem;
font-weight: 500;
color: var(--gray-700);
}
.warning-list li i {
color: var(--danger-color);
margin-right: 0.75rem;
font-size: 1.1rem;
}
/* Questionnaire Info */
.questionnaire-info {
background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%);
border-radius: var(--border-radius-lg);
padding: 2rem;
margin-bottom: 2.5rem;
border: 1px solid var(--gray-200);
}
.info-header {
display: flex;
align-items: center;
margin-bottom: 1.5rem;
}
.info-icon {
background: var(--info-color);
color: white;
width: 48px;
height: 48px;
border-radius: var(--border-radius-md);
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
font-size: 1.5rem;
}
.info-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--gray-800);
margin: 0;
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.info-item {
background: white;
padding: 1.25rem;
border-radius: var(--border-radius-md);
border: 1px solid var(--gray-200);
box-shadow: var(--shadow-sm);
}
.info-label {
font-size: 0.875rem;
font-weight: 600;
color: var(--gray-500);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 0.5rem;
}
.info-value {
font-size: 1.1rem;
font-weight: 600;
color: var(--gray-800);
word-break: break-word;
}
.info-description {
background: white;
padding: 1.25rem;
border-radius: var(--border-radius-md);
border: 1px solid var(--gray-200);
box-shadow: var(--shadow-sm);
max-height: 120px;
overflow-y: auto;
}
.info-description-content {
color: var(--gray-700);
line-height: 1.6;
font-size: 0.95rem;
}
/* Stats Section */
.stats-section {
background: white;
border-radius: var(--border-radius-lg);
padding: 2rem;
margin-bottom: 2.5rem;
border: 1px solid var(--gray-200);
box-shadow: var(--shadow-md);
}
.stats-title {
font-size: 1.25rem;
font-weight: 700;
color: var(--gray-800);
margin-bottom: 1.5rem;
display: flex;
align-items: center;
}
.stats-title i {
margin-right: 0.75rem;
color: var(--info-color);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
}
.stat-card {
background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%);
padding: 1.5rem;
border-radius: var(--border-radius-md);
text-align: center;
border: 1px solid var(--gray-200);
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.stat-number {
font-size: 2rem;
font-weight: 800;
color: var(--danger-color);
display: block;
margin-bottom: 0.5rem;
}
.stat-label {
font-size: 0.875rem;
font-weight: 600;
color: var(--gray-600);
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Action Buttons */
.action-section {
background: var(--gray-50);
padding: 2.5rem 2rem;
border-top: 1px solid var(--gray-200);
}
.action-buttons {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.btn {
border-radius: var(--border-radius-md);
font-weight: 600;
padding: 1rem 2rem;
font-size: 1rem;
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.75rem;
box-shadow: var(--shadow-md);
position: relative;
overflow: hidden;
min-width: 140px;
justify-content: center;
}
.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-danger-custom {
background: linear-gradient(135deg, var(--danger-color) 0%, var(--danger-dark) 100%);
color: white;
}
.btn-danger-custom:hover {
background: linear-gradient(135deg, var(--danger-dark) 0%, #b91c1c 100%);
transform: translateY(-2px);
box-shadow: var(--shadow-xl);
color: white;
}
.btn-secondary-custom {
background: linear-gradient(135deg, var(--gray-500) 0%, var(--gray-600) 100%);
color: white;
}
.btn-secondary-custom:hover {
background: linear-gradient(135deg, var(--gray-600) 0%, var(--gray-700) 100%);
transform: translateY(-2px);
box-shadow: var(--shadow-xl);
color: white;
}
/* Enhanced Modal */
.modal-content {
border-radius: var(--border-radius-xl);
border: none;
box-shadow: var(--shadow-2xl);
overflow: hidden;
}
.modal-header-danger {
background: linear-gradient(135deg, var(--danger-color) 0%, var(--danger-dark) 100%);
color: white;
padding: 2rem 2.5rem;
border: none;
text-align: center;
}
.modal-title-custom {
font-size: 1.5rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
}
.modal-body-custom {
padding: 2.5rem;
text-align: center;
}
.modal-message {
font-size: 1.25rem;
color: var(--gray-700);
margin-bottom: 1.5rem;
line-height: 1.6;
}
.modal-warning {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border: 1px solid var(--warning-color);
border-radius: var(--border-radius-md);
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.modal-footer-custom {
padding: 2rem 2.5rem;
border: none;
background: var(--gray-50);
display: flex;
gap: 1rem;
justify-content: center;
}
/* Success State */
.success-message {
color: var(--success-color);
font-size: 1.5rem;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
}
/* Loading State */
.btn.loading {
opacity: 0.7;
pointer-events: none;
}
.btn.loading::after {
content: "";
width: 18px;
height: 18px;
margin-left: 0.75rem;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@@keyframes spin {
0%
{
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Responsive Design */
@@media (max-width: 768px) {
.container
{
padding: 0 1rem;
}
.delete-content {
padding: 2rem 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
gap: 1rem;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.action-buttons {
flex-direction: column;
}
.btn {
width: 100%;
}
}
/* Animations */
.fade-in {
animation: fadeIn 0.6s ease-out;
}
@@keyframes fadeIn {
from
{
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
<div class="container">
<div class="delete-card fade-in">
<!-- Header Section -->
<div class="delete-header">
<div class="delete-icon">
<i class="bi bi-trash3-fill"></i>
</div> </div>
<h1 class="delete-title">Delete Questionnaire</h1>
<p class="delete-subtitle">This action will permanently remove this questionnaire and all associated data</p>
</div> </div>
</div>
<!-- Content Section -->
<div class="delete-content">
<!-- Warning Alert -->
<div class="warning-alert">
<div class="warning-content">
<h3 class="warning-title">⚠️ Critical Warning</h3>
<ul class="warning-list">
<li><i class="bi bi-x-circle-fill"></i>This action cannot be undone</li>
<li><i class="bi bi-x-circle-fill"></i>All questions and answers will be permanently deleted</li>
<li><i class="bi bi-x-circle-fill"></i>Any survey responses will be lost forever</li>
<li><i class="bi bi-x-circle-fill"></i>Associated data and analytics will be removed</li>
</ul>
</div>
</div>
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" aria-hidden="true"> <!-- Questionnaire Information -->
<div class="modal-dialog" role="document"> <div class="questionnaire-info">
<div class="modal-content"> <div class="info-header">
<div class="modal-header"> <div class="info-icon">
<h5 class="modal-title" id="deleteModalLabel">Delete Confirmation</h5> <i class="bi bi-file-text"></i>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> </div>
<span aria-hidden="true">&times;</span> <h2 class="info-title">Questionnaire Details</h2>
</button>
</div> </div>
<div class="modal-body">
<p id="deleteMessage">Are you sure you want to delete this questionnaire</p> <div class="info-grid">
<p class="text-danger">If you delete, you can't recover it.</p> <div class="info-item">
<input type="text" class="form-control" id="deleteConfirmation" placeholder="Type the questionnaire name to confirm"> <div class="info-label">Title</div>
<div class="info-value">@Model.Title</div>
</div>
<div class="info-item">
<div class="info-label">Questionnaire ID</div>
<div class="info-value">#@Model.Id</div>
</div>
</div> </div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <div class="info-description">
<button type="button" class="btn btn-danger" id="deleteButton" disabled>Delete</button> <div class="info-label">Description</div>
<div class="info-description-content">
@if (!string.IsNullOrEmpty(Model.Description))
{
@Html.Raw(Model.Description)
}
else
{
<em class="text-muted">No description provided</em>
}
</div>
</div>
</div>
<!-- Statistics -->
<div class="stats-section">
<h3 class="stats-title">
<i class="bi bi-graph-up"></i>
Content Statistics
</h3>
<div class="stats-grid">
<div class="stat-card">
<span class="stat-number">@Model.Questions.Count</span>
<span class="stat-label">Questions</span>
</div>
<div class="stat-card">
<span class="stat-number">@Model.Questions.Sum(q => q.Answers.Count)</span>
<span class="stat-label">Total Answers</span>
</div>
<div class="stat-card">
<span class="stat-number">@Model.Questions.Count(q => q.Answers.Any(a => a.IsOtherOption))</span>
<span class="stat-label">Other Options</span>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Action Section -->
<div class="action-section">
<div class="action-buttons">
<button type="button" class="btn btn-danger-custom" data-bs-toggle="modal" data-bs-target="#deleteModal">
<i class="bi bi-trash3-fill"></i>
Delete Questionnaire
</button>
<a asp-action="Index" class="btn btn-secondary-custom">
<i class="bi bi-arrow-left"></i>
Back to List
</a>
</div>
</div>
</div> </div>
</div>
<!-- Enhanced Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<!-- Delete Confirmation Modal --> <div class="modal-content">
<div class="modal-header-danger">
<h5 class="modal-title-custom" id="deleteModalLabel">
<i class="bi bi-exclamation-triangle-fill"></i>
Confirm Deletion
</h5>
</div>
<div class="modal-body-custom">
<p class="modal-message" id="deleteMessage">
Are you absolutely sure you want to delete this questionnaire?
</p>
<div class="modal-warning">
<i class="bi bi-shield-exclamation"></i>
<strong>This action is irreversible!</strong> Once deleted, this questionnaire and all its data cannot be recovered.
</div>
</div>
<div class="modal-footer-custom">
<button type="button" class="btn btn-secondary-custom" data-bs-dismiss="modal">
<i class="bi bi-x"></i>
Cancel
</button>
<button type="button" class="btn btn-danger-custom" id="deleteButton">
<i class="bi bi-trash3-fill"></i>
Yes, Delete Forever
</button>
</div>
</div>
</div>
</div>
@section Scripts { @section Scripts {
<script src="https://cdnjs.cloudflare.com/ajax/libs/ckeditor/4.11.4/ckeditor.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ckeditor/4.11.4/ckeditor.js"></script>
<!-- Add these links in the <head> section of your HTML file --> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js" integrity="sha512-aVKKRRi/Q/YV+4mjoKBsE4x3H+BkegoM/em46NNlCqNTmUYADjBbeNefNxYV7giUp0VxICtqdrbqU7iVaeZNXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script> <script>
CKEDITOR.replace("Description"); $(document).ready(function () {
</script> var itemId = @Model.Id;
@{
<partial name="_ValidationScriptsPartial" />
}
<script>
$(document).ready(function () { // Enhanced delete button click event
var itemId = @Model.Id; // Assuming you can get the item ID from the model $('#deleteButton').on('click', function () {
var $btn = $(this);
var $modal = $('#deleteModal');
// Enable delete button when the input matches the item name // Add loading state
$('#deleteConfirmation').on('input', function () { $btn.addClass('loading').prop('disabled', true);
var itemName = '@Model.Title'; // Item name from the model
var inputText = $(this).val().trim().toLowerCase();
var isMatch = inputText === itemName.toLowerCase();
// var space = $(this).val.space();
$('#deleteButton').prop('disabled', !isMatch);
});
// Clear input and disable button when modal is hidden // Make AJAX request to delete the item
$('#deleteModal').on('hidden.bs.modal', function () { $.ajax({
$('#deleteConfirmation').val(''); url: '/admin/Questionnaire/Delete/' + itemId,
$('#deleteButton').prop('disabled', true); type: 'POST',
}); success: function (result) {
// Update modal content to show success
$('.modal-header-danger').removeClass('modal-header-danger').css({
'background': 'linear-gradient(135deg, #10b981 0%, #059669 100%)'
});
// Delete button click event $('.modal-title-custom').html('<i class="bi bi-check-circle-fill"></i> Success');
$('#deleteButton').on('click', function () {
// Make an AJAX request to delete the item
$.ajax({
url: '/admin/Questionnaire/Delete/' + itemId,
type: 'POST', // or 'DELETE' if you have a dedicated delete action
success: function (result) {
// Hide the confirmation details
$('#deleteConfirmation, .text-danger').hide();
$('#deleteButton').hide();
// Show the success message
$('#deleteMessage').text('Questionnaire deleted successfully.').addClass('text-success h4').show();
// Show the modal
$('#deleteModal').modal('show');
// Automatically close the modal after 4 seconds
setTimeout(function () {
$('#deleteModal').modal('hide');
// Redirect to the index action method after closing the modal
window.location.href = '/admin/Questionnaire/Index';
}, 1000);
},
error: function (error) {
// Handle error
$('#deleteMessage').text('Failed to delete item.').show();
// Show the modal
$('#deleteModal').modal('show');
// Automatically close the modal after 4 seconds
setTimeout(function () {
$('#deleteModal').modal('hide');
}, 1000);
}
});
});
$('#deleteMessage').html('<div class="success-message"><i class="bi bi-check-circle-fill"></i>Questionnaire deleted successfully!</div>');
}); $('.modal-warning').hide();
$('.modal-footer-custom').hide();
// Automatically redirect after showing success
setTimeout(function () {
$modal.modal('hide');
window.location.href = '/admin/Questionnaire/Index';
}, 2000);
},
error: function (xhr, status, error) {
// Remove loading state
$btn.removeClass('loading').prop('disabled', false);
</script> // Show error message
$('#deleteMessage').html('<div class="text-danger"><i class="bi bi-exclamation-triangle-fill"></i> Failed to delete questionnaire. Please try again.</div>');
<script> // Auto-hide error message after 3 seconds
$(document).ready(function () { setTimeout(function () {
$(".readMoreBtn").click(function () { $modal.modal('hide');
$(this).closest('.text-danger').find('.more-title').toggle(); }, 3000);
$(this).text(function (_, text) { }
return text === "Read More" ? "Read Less" : "Read More";
});
}); });
}); });
// Reset modal when hidden
$('#deleteModal').on('hidden.bs.modal', function () {
// Reset button state
$('#deleteButton').removeClass('loading').prop('disabled', false);
// Reset modal content if needed
$('.modal-header-danger').css({
'background': 'linear-gradient(135deg, var(--danger-color) 0%, var(--danger-dark) 100%)'
});
$('.modal-title-custom').html('<i class="bi bi-exclamation-triangle-fill"></i> Confirm Deletion');
$('#deleteMessage').text('Are you absolutely sure you want to delete this questionnaire?');
$('.modal-warning').show();
$('.modal-footer-custom').show();
});
});
</script> </script>
} }

View file

@ -1,121 +1,804 @@
@model QuestionnaireViewModel @model QuestionnaireViewModel
@{ @{
ViewData["Title"] = "Details"; ViewData["Title"] = "Questionnaire Details";
} }
<style>
/* Modern Design System */
@@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
:root {
--primary-color: #6366f1;
--primary-light: #8b5cf6;
--primary-dark: #4338ca;
--success-color: #10b981;
--success-light: #34d399;
--success-dark: #059669;
--info-color: #06b6d4;
--info-light: #22d3ee;
--info-dark: #0891b2;
--warning-color: #f59e0b;
--warning-light: #fbbf24;
--warning-dark: #d97706;
--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: 24px;
}
<div class="container p-4"> * {
<div> font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
<a class="btn btn-primary" asp-action="Index">Back to List</a> }
body {
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
min-height: 100vh;
padding: 2rem 0;
}
.container {
max-width: 1200px;
}
/* Header Section */
.page-header {
background: white;
border-radius: var(--border-radius-xl);
box-shadow: var(--shadow-xl);
padding: 2.5rem;
margin-bottom: 2rem;
position: relative;
overflow: hidden;
}
.page-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--primary-color), var(--primary-light), var(--info-color));
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 1.5rem;
}
.header-info {
display: flex;
align-items: center;
gap: 1.5rem;
}
.header-icon {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
color: white;
width: 64px;
height: 64px;
border-radius: var(--border-radius-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.75rem;
box-shadow: var(--shadow-lg);
}
.header-text h1 {
font-size: 2rem;
font-weight: 800;
color: var(--gray-800);
margin: 0 0 0.5rem;
}
.header-subtitle {
color: var(--gray-600);
font-size: 1rem;
font-weight: 500;
}
.back-button {
background: linear-gradient(135deg, var(--gray-500) 0%, var(--gray-600) 100%);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: var(--border-radius-md);
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
transition: all 0.3s ease;
box-shadow: var(--shadow-md);
}
.back-button:hover {
background: linear-gradient(135deg, var(--gray-600) 0%, var(--gray-700) 100%);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
color: white;
text-decoration: none;
}
/* Main Content */
.main-content {
background: white;
border-radius: var(--border-radius-xl);
box-shadow: var(--shadow-xl);
overflow: hidden;
margin-bottom: 2rem;
}
/* Overview Section */
.overview-section {
background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%);
padding: 2.5rem;
border-bottom: 1px solid var(--gray-200);
}
.overview-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
}
.overview-icon {
background: var(--info-color);
color: white;
width: 48px;
height: 48px;
border-radius: var(--border-radius-md);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
.overview-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--gray-800);
margin: 0;
}
.overview-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
}
.overview-card {
background: white;
padding: 1.5rem;
border-radius: var(--border-radius-lg);
border: 1px solid var(--gray-200);
box-shadow: var(--shadow-sm);
transition: all 0.3s ease;
}
.overview-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.overview-card-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1rem;
}
.overview-card-icon {
width: 36px;
height: 36px;
border-radius: var(--border-radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.1rem;
color: white;
}
.overview-card-icon.warning {
background: var(--warning-color);
}
.overview-card-icon.primary {
background: var(--primary-color);
}
.overview-card-icon.success {
background: var(--success-color);
}
.overview-card-icon.info {
background: var(--info-color);
}
.overview-card-label {
font-size: 0.875rem;
font-weight: 600;
color: var(--gray-500);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.overview-card-value {
font-size: 1.5rem;
font-weight: 700;
color: var(--gray-800);
word-break: break-word;
line-height: 1.3;
}
.description-card {
grid-column: 1 / -1;
}
.description-content {
color: var(--gray-700);
line-height: 1.6;
font-size: 1rem;
margin-top: 0.5rem;
}
/* Questions Section */
.questions-section {
padding: 2.5rem;
}
.questions-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}
.questions-title {
display: flex;
align-items: center;
gap: 1rem;
}
.questions-icon {
background: var(--success-color);
color: white;
width: 48px;
height: 48px;
border-radius: var(--border-radius-md);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
.questions-title h2 {
font-size: 1.5rem;
font-weight: 700;
color: var(--gray-800);
margin: 0;
}
.questions-count {
background: linear-gradient(135deg, var(--success-color) 0%, var(--success-dark) 100%);
color: white;
padding: 0.5rem 1rem;
border-radius: var(--border-radius-md);
font-weight: 600;
font-size: 0.875rem;
box-shadow: var(--shadow-sm);
}
/* Question Cards */
.questions-grid {
display: grid;
gap: 2rem;
}
.question-card {
background: linear-gradient(135deg, var(--gray-50) 0%, white 100%);
border-radius: var(--border-radius-lg);
border: 1px solid var(--gray-200);
box-shadow: var(--shadow-md);
overflow: hidden;
transition: all 0.3s ease;
}
.question-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-xl);
}
.question-header {
background: linear-gradient(135deg, var(--success-color) 0%, var(--success-dark) 100%);
color: white;
padding: 1.5rem;
position: relative;
overflow: hidden;
}
.question-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 20"><defs><radialGradient id="a" cx="50%" cy="0%" r="100%"><stop offset="0%" stop-color="white" stop-opacity="0.1"/><stop offset="100%" stop-color="white" stop-opacity="0"/></radialGradient></defs><rect width="100" height="20" fill="url(%23a)"/></svg>');
opacity: 0.3;
}
.question-meta {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
position: relative;
z-index: 1;
}
.question-id {
background: rgba(255, 255, 255, 0.2);
padding: 0.375rem 0.75rem;
border-radius: var(--border-radius-sm);
font-weight: 600;
font-size: 0.875rem;
}
.question-type {
background: rgba(255, 255, 255, 0.2);
padding: 0.375rem 0.75rem;
border-radius: var(--border-radius-sm);
font-weight: 600;
font-size: 0.875rem;
}
.question-text {
font-size: 1.25rem;
font-weight: 700;
margin: 0;
position: relative;
z-index: 1;
line-height: 1.4;
}
/* Answers Section */
.answers-section {
padding: 2rem;
}
.answers-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.answers-icon {
background: var(--info-color);
color: white;
width: 32px;
height: 32px;
border-radius: var(--border-radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
}
.answers-title {
font-size: 1.1rem;
font-weight: 600;
color: var(--gray-800);
margin: 0;
}
.answers-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.answer-card {
background: white;
border: 2px solid var(--gray-200);
border-radius: var(--border-radius-md);
padding: 1rem;
transition: all 0.3s ease;
position: relative;
}
.answer-card:hover {
border-color: var(--info-color);
box-shadow: 0 0 0 3px rgba(6, 182, 212, 0.1);
transform: translateY(-1px);
}
.answer-card.other-option {
border-color: var(--warning-color);
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 20%, white 100%);
}
.answer-card.other-option::before {
content: "OTHER";
position: absolute;
top: -8px;
left: 12px;
background: var(--warning-color);
color: white;
padding: 0.25rem 0.5rem;
border-radius: var(--border-radius-sm);
font-size: 0.625rem;
font-weight: 700;
letter-spacing: 0.5px;
}
.answer-meta {
display: flex;
align-items: center;
justify-content: between;
gap: 0.75rem;
margin-bottom: 0.75rem;
}
.answer-id {
background: linear-gradient(135deg, var(--info-color) 0%, var(--info-dark) 100%);
color: white;
padding: 0.25rem 0.5rem;
border-radius: var(--border-radius-sm);
font-weight: 600;
font-size: 0.75rem;
}
.answer-text {
font-size: 1rem;
font-weight: 600;
color: var(--gray-800);
line-height: 1.4;
word-break: break-word;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 3rem;
color: var(--gray-500);
}
.empty-state i {
font-size: 3rem;
margin-bottom: 1rem;
color: var(--gray-400);
}
.empty-state h3 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
/* Stats Summary */
.stats-summary {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
color: white;
padding: 2rem;
margin-bottom: 2rem;
border-radius: var(--border-radius-lg);
box-shadow: var(--shadow-lg);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 1.5rem;
text-align: center;
}
.stat-item {
padding: 1rem;
background: rgba(255, 255, 255, 0.1);
border-radius: var(--border-radius-md);
backdrop-filter: blur(10px);
}
.stat-number {
font-size: 2rem;
font-weight: 800;
display: block;
margin-bottom: 0.25rem;
}
.stat-label {
font-size: 0.875rem;
font-weight: 500;
opacity: 0.9;
}
/* Footer Actions */
.footer-actions {
text-align: center;
margin-top: 2rem;
}
/* Responsive Design */
@@media (max-width: 768px) {
.container
{
padding: 0 1rem;
}
.header-content {
flex-direction: column;
align-items: flex-start;
}
.header-info {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.overview-grid {
grid-template-columns: 1fr;
}
.questions-header {
flex-direction: column;
align-items: flex-start;
}
.answers-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* Animations */
.fade-in {
animation: fadeIn 0.6s ease-out;
}
@@keyframes fadeIn {
from
{
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-in {
animation: slideIn 0.4s ease-out;
}
@@keyframes slideIn {
from
{
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
</style>
<div class="container">
<!-- Page Header -->
<div class="page-header fade-in">
<div class="header-content">
<div class="header-info">
<div class="header-icon">
<i class="bi bi-file-text-fill"></i>
</div>
<div class="header-text">
<h1>Questionnaire Details</h1>
<p class="header-subtitle">Complete overview of questionnaire structure and content</p>
</div>
</div>
<a asp-action="Index" class="back-button">
<i class="bi bi-arrow-left"></i>
Back to List
</a>
</div>
</div> </div>
<div class="card"> <!-- Stats Summary -->
<div class="card-header"> <div class="stats-summary fade-in">
Details <div class="stats-grid">
<div class="stat-item">
<span class="stat-number">@Model.Questions.Count</span>
<span class="stat-label">Questions</span>
</div>
<div class="stat-item">
<span class="stat-number">@Model.Questions.Sum(q => q.Answers.Count)</span>
<span class="stat-label">Total Answers</span>
</div>
<div class="stat-item">
<span class="stat-number">@Model.Questions.Count(q => q.Answers.Any(a => a.IsOtherOption))</span>
<span class="stat-label">Other Options</span>
</div>
<div class="stat-item">
<span class="stat-number">@Model.Questions.Select(q => q.Type).Distinct().Count()</span>
<span class="stat-label">Question Types</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content fade-in">
<!-- Overview Section -->
<div class="overview-section">
<div class="overview-header">
<div class="overview-icon">
<i class="bi bi-info-circle-fill"></i>
</div>
<h2 class="overview-title">Overview</h2>
</div>
<div class="overview-grid">
<div class="overview-card slide-in">
<div class="overview-card-header">
<div class="overview-card-icon warning">
<i class="bi bi-hash"></i>
</div>
<span class="overview-card-label">ID</span>
</div>
<div class="overview-card-value">#@Model.Id</div>
</div>
<div class="overview-card slide-in">
<div class="overview-card-header">
<div class="overview-card-icon primary">
<i class="bi bi-card-heading"></i>
</div>
<span class="overview-card-label">Title</span>
</div>
<div class="overview-card-value">@Model.Title</div>
</div>
<div class="overview-card description-card slide-in">
<div class="overview-card-header">
<div class="overview-card-icon info">
<i class="bi bi-card-text"></i>
</div>
<span class="overview-card-label">Description</span>
</div>
<div class="description-content">
@if (!string.IsNullOrEmpty(Model.Description))
{
@Html.Raw(Model.Description)
}
else
{
<em class="text-muted">No description provided</em>
}
</div>
</div>
</div>
</div> </div>
<div class="card-body shadow rounded "> <!-- Questions Section -->
<div class="card-title"> <div class="questions-section">
<div class="questions-header">
<div class="questions-title">
<h4>Questionnaire details</h4> <div class="questions-icon">
<br /> <i class="bi bi-question-circle-fill"></i>
<div> </div>
<span class="badge badge-warning shadow text-white m-1 p-2">ID</span> <h2>Questions</h2>
</div> </div>
<div> <div class="questions-count">
<span class="badge badge-primary shadow m-1 p-2">Questionnaire</span> @Model.Questions.Count Question@(Model.Questions.Count != 1 ? "s" : "")
</div>
<div>
<span class="badge badge-success shadow m-1 p-2">Question</span>
</div> </div>
<div>
<span class="badge badge-info shadow m-1 p-2">Answer</span>
</div>
</div> </div>
<table class="table table-responsive table-bordered table-hover ">
<thead>
<tr >
<th scope="col" class="text-warning h5">ID</th>
<th scope="col" class="text-primary h5">Questionnaire</th>
<th scope="col" class="text-success h5 ">Questions</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row"><span class="badge p-2 m-1 bg-warning shadow-sm rounded">@Model.Id</span></th>
<th scope="row">
<span class="badge p-2 m-1 bg-primary shadow-sm rounded">@Model.Title</span> @if (Model.Questions.Any())
{
<div class="questions-grid">
@foreach (var question in Model.Questions.Select((q, index) => new { Question = q, Index = index }))
{
<div class="question-card slide-in">
<div class="question-header">
<div class="question-meta">
<span class="question-id">ID: @question.Question.Id</span>
<span class="question-type">@question.Question.Type</span>
</div>
<h3 class="question-text">
Question @(question.Index + 1): @question.Question.Text
</h3>
</div>
</th> <div class="answers-section">
<div class="answers-header">
<div class="answers-icon">
<i class="bi bi-list-ul"></i>
</div>
<h4 class="answers-title">
Answers (@question.Question.Answers.Count)
</h4>
</div>
@if (question.Question.Answers.Any())
<td class="h6">
<table class="table table-responsive w-100 d-block d-md-table table-bordered table-hover ">
<tr >
<th class="text-success">Id</th>
<th class="text-success">Question</th>
<th class="text-success">Question Type</th>
<th class="text-info">Answers</th>
</tr>
@foreach (var question in Model.Questions)
{ {
<tr> <div class="answers-grid">
<td> <span class="badge p-2 m-1 bg-success ">@question.Id</span></td> @foreach (var answer in question.Question.Answers)
<td> {
<span class="badge p-2 m-1 bg-success ">@question.Text</span> <div class="answer-card @(answer.IsOtherOption ? "other-option" : "")">
</td> <div class="answer-meta">
<td> <span class="answer-id">ID: @answer.Id</span>
<span class="badge p-2 m-1 bg-success ">@question.Type</span> </div>
</td> <div class="answer-text">@answer.Text</div>
<td> </div>
<table class="table-borderless"> }
<tr> </div>
<th class="text-info">Id</th>
<th class="text-info">Answer</th>
</tr>
@foreach (var answer in question.Answers)
{
<tr>
<td>
<span class="badge p-2 m-1 bg-info shadow-sm">@answer.Id</span>
</td>
<td>
<span class="badge p-2 m-1 bg-info shadow-sm">@answer.Text</span>
</td>
</tr>
}
</table>
</td>
</tr>
} }
</table> else
{
<div class="empty-state">
</td> <i class="bi bi-inbox"></i>
<h3>No Answers</h3>
</tr> <p>This question doesn't have any answer options.</p>
</div>
</tbody> }
</div>
</table> </div>
<div> }
<a class="btn btn-primary" asp-action="Index">Back to List</a> </div>
}
</div> else
{
<div class="empty-state">
<i class="bi bi-question-circle"></i>
<h3>No Questions</h3>
<p>This questionnaire doesn't contain any questions yet.</p>
</div>
}
</div> </div>
</div> </div>
<!-- Footer Actions -->
<div class="footer-actions">
<a asp-action="Index" class="back-button">
<i class="bi bi-arrow-left"></i>
Back to List
</a>
</div>
</div> </div>

View file

@ -263,6 +263,50 @@
color: white; color: white;
} }
/* Bulk Selection Controls */
.bulk-selection-controls {
background: white;
border-radius: var(--border-radius-lg);
padding: 2rem;
margin: 2rem 0;
box-shadow: var(--shadow-lg);
border: 1px solid var(--gray-200);
display: none;
transition: all 0.3s ease;
}
.bulk-selection-controls.show {
display: block;
animation: slideDown 0.3s ease;
}
@@keyframes slideDown {
from
{
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.selection-summary {
background: linear-gradient(135deg, var(--info-color) 0%, #0891b2 100%);
color: white;
padding: 1rem 1.5rem;
border-radius: var(--border-radius-md);
margin-bottom: 1.5rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.75rem;
}
/* Question Container Enhancements */ /* Question Container Enhancements */
#customCard { #customCard {
background: white; background: white;
@ -328,6 +372,96 @@
transform: scaleY(1); transform: scaleY(1);
} }
.question.selected {
border-color: var(--danger-color);
background: linear-gradient(135deg, #fef2f2 0%, #fecaca 10%, white 100%);
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1), var(--shadow-lg);
}
.question.selected::before {
background: linear-gradient(135deg, var(--danger-color), #dc2626);
transform: scaleY(1);
}
/* Question Checkbox Styling */
.question-checkbox {
position: absolute;
top: 1.5rem;
right: 1.5rem;
z-index: 10;
}
.custom-checkbox {
width: 24px;
height: 24px;
border: 2px solid var(--gray-300);
border-radius: 6px;
background: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: var(--shadow-sm);
}
.custom-checkbox:hover {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.custom-checkbox.checked {
background: var(--danger-color);
border-color: var(--danger-color);
color: white;
}
.custom-checkbox input {
display: none;
}
/* Master checkbox styling */
.master-checkbox-container {
background: white;
border-radius: var(--border-radius-md);
padding: 1.5rem;
margin: 2rem 0;
box-shadow: var(--shadow-md);
border: 1px solid var(--gray-200);
display: flex;
align-items: center;
gap: 1rem;
}
.master-checkbox {
width: 28px;
height: 28px;
border: 2px solid var(--gray-300);
border-radius: 8px;
background: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: var(--shadow-sm);
}
.master-checkbox:hover {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.master-checkbox.checked {
background: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.master-checkbox input {
display: none;
}
/* Question Separator */ /* Question Separator */
.question-separator { .question-separator {
display: flex; display: flex;
@ -366,11 +500,12 @@
/* Question Title Styling */ /* Question Title Styling */
.question-title { .question-title {
background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%); background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%);
padding: 2rem; padding: 2rem 2rem 2rem 3rem;
border-radius: var(--border-radius-md); border-radius: var(--border-radius-md);
border: 1px solid var(--gray-200); border: 1px solid var(--gray-200);
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
transition: all 0.3s ease; transition: all 0.3s ease;
position: relative;
} }
.question-title:hover { .question-title:hover {
@ -689,7 +824,7 @@
<!-- Basic Information --> <!-- Basic Information -->
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-12">
<div class="form-group"> <div class="form-group">
<label asp-for="Title" class="control-label"> <label asp-for="Title" class="control-label">
<i class="bi bi-card-heading me-2"></i>Survey Title <i class="bi bi-card-heading me-2"></i>Survey Title
@ -698,7 +833,7 @@
<span asp-validation-for="Title" class="text-danger"></span> <span asp-validation-for="Title" class="text-danger"></span>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-12">
<div class="form-group"> <div class="form-group">
<label asp-for="Description" class="control-label"> <label asp-for="Description" class="control-label">
<i class="bi bi-card-text me-2"></i>Description <i class="bi bi-card-text me-2"></i>Description
@ -715,10 +850,48 @@
<h4><i class="bi bi-question-circle me-2"></i>Edit Questions or Add New Questions</h4> <h4><i class="bi bi-question-circle me-2"></i>Edit Questions or Add New Questions</h4>
</div> </div>
<!-- Master Checkbox for Select All -->
<div class="master-checkbox-container">
<div class="master-checkbox" id="selectAllCheckbox">
<input type="checkbox" id="selectAllInput">
<i class="bi bi-check" style="display: none;"></i>
</div>
<label for="selectAllInput" class="mb-0 font-weight-bold">
<i class="bi bi-check-square me-2"></i>Select All Questions
</label>
<div class="ms-auto">
<span id="selectedCount" class="badge bg-primary">0 selected</span>
</div>
</div>
<!-- Bulk Selection Controls -->
<div class="bulk-selection-controls" id="bulkControls">
<div class="selection-summary">
<i class="bi bi-info-circle"></i>
<span id="bulkSelectionText">0 questions selected</span>
</div>
<div class="d-flex gap-3 flex-wrap">
<button type="button" class="btn btn-danger" id="deleteSelectedQuestions">
<i class="bi bi-trash-fill"></i> Delete Selected Questions
</button>
<button type="button" class="btn btn-secondary" id="deselectAll">
<i class="bi bi-x-circle"></i> Deselect All
</button>
</div>
</div>
<div id="questionsContainer"> <div id="questionsContainer">
@for (int i = 0; i < Model.Questions.Count; i++) @for (int i = 0; i < Model.Questions.Count; i++)
{ {
<div class="question fade-in"> <div class="question fade-in" data-question-index="@i">
<!-- Question Checkbox -->
<div class="question-checkbox">
<div class="custom-checkbox question-select-checkbox">
<input type="checkbox" class="question-checkbox-input">
<i class="bi bi-check" style="display: none;"></i>
</div>
</div>
<div class="question-separator"> <div class="question-separator">
<div class="line-container"> <div class="line-container">
<hr class="line-start"> <hr class="line-start">
@ -837,7 +1010,7 @@
</div> </div>
</div> </div>
<!-- Enhanced Confirmation Modal --> <!-- Enhanced Confirmation Modal for Single Question -->
<div class="modal fade" id="confirmDeleteModal" tabindex="-1" aria-labelledby="confirmDeleteModalLabel" aria-hidden="true"> <div class="modal fade" id="confirmDeleteModal" tabindex="-1" aria-labelledby="confirmDeleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
@ -866,6 +1039,50 @@
</div> </div>
</div> </div>
<!-- Enhanced Bulk Delete Confirmation Modal -->
<div class="modal fade" id="confirmBulkDeleteModal" tabindex="-1" aria-labelledby="confirmBulkDeleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmBulkDeleteModalLabel">
<i class="bi bi-exclamation-triangle me-2"></i>Confirm Multiple Deletion
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>Critical Warning:</strong> You are about to delete <span id="deletionCount" class="fw-bold">0</span> question(s).
</div>
<p class="mb-3">This action will permanently remove:</p>
<ul class="list-unstyled">
<li class="mb-2"><i class="bi bi-check text-danger me-2"></i><span id="questionCount">0</span> questions</li>
<li class="mb-2"><i class="bi bi-check text-danger me-2"></i>All associated answers</li>
<li class="mb-2"><i class="bi bi-check text-danger me-2"></i>All related user responses</li>
</ul>
<div class="alert alert-warning">
<i class="bi bi-info-circle me-2"></i>
<strong>Note:</strong> This action cannot be undone. Please ensure you want to proceed with this bulk deletion.
</div>
<div class="preview-container" id="deletionPreview">
<h6 class="fw-bold mb-3">Questions to be deleted:</h6>
<div class="preview-list" style="max-height: 200px; overflow-y: auto; background: #f8f9fa; padding: 1rem; border-radius: 8px;">
<!-- Preview questions will be populated here -->
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="bi bi-x"></i> Cancel
</button>
<button type="button" class="btn btn-danger" id="confirmBulkDelete">
<i class="bi bi-trash"></i> Delete All Selected Questions
</button>
</div>
</div>
</div>
</div>
@section Scripts { @section Scripts {
<script src="https://cdnjs.cloudflare.com/ajax/libs/ckeditor/4.11.4/ckeditor.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/ckeditor/4.11.4/ckeditor.js"></script>
<script> <script>
@ -879,11 +1096,250 @@
<script> <script>
$(document).ready(function () { $(document).ready(function () {
let questionToDelete = null; let questionToDelete = null;
let selectedQuestions = [];
// Update selection UI
function updateSelectionUI() {
const selectedCount = selectedQuestions.length;
$('#selectedCount').text(selectedCount + ' selected');
$('#bulkSelectionText').text(selectedCount + ' question' + (selectedCount !== 1 ? 's' : '') + ' selected');
if (selectedCount > 0) {
$('#bulkControls').addClass('show');
} else {
$('#bulkControls').removeClass('show');
}
// Update master checkbox state
const totalQuestions = $('.question-checkbox-input').length;
const masterCheckbox = $('#selectAllCheckbox');
const masterInput = $('#selectAllInput');
if (selectedCount === 0) {
masterCheckbox.removeClass('checked');
masterInput.prop('checked', false);
masterCheckbox.find('i').hide();
} else if (selectedCount === totalQuestions) {
masterCheckbox.addClass('checked');
masterInput.prop('checked', true);
masterCheckbox.find('i').show();
} else {
masterCheckbox.removeClass('checked');
masterInput.prop('checked', false);
masterCheckbox.find('i').hide();
}
}
// Handle individual question checkbox clicks
$(document).on('change', '.question-checkbox-input', function() {
const questionContainer = $(this).closest('.question');
const questionIndex = questionContainer.data('question-index');
const checkbox = $(this).closest('.custom-checkbox');
if ($(this).is(':checked')) {
if (!selectedQuestions.includes(questionIndex)) {
selectedQuestions.push(questionIndex);
questionContainer.addClass('selected');
checkbox.addClass('checked');
checkbox.find('i').show();
}
} else {
selectedQuestions = selectedQuestions.filter(q => q !== questionIndex);
questionContainer.removeClass('selected');
checkbox.removeClass('checked');
checkbox.find('i').hide();
}
updateSelectionUI();
});
// Handle checkbox visual clicks
$(document).on('click', '.question-select-checkbox', function() {
const input = $(this).find('input');
input.prop('checked', !input.is(':checked')).trigger('change');
});
// Handle master checkbox
$('#selectAllCheckbox').click(function() {
const shouldSelectAll = selectedQuestions.length !== $('.question-checkbox-input').length;
$('.question-checkbox-input').each(function() {
const questionContainer = $(this).closest('.question');
const questionIndex = questionContainer.data('question-index');
const checkbox = $(this).closest('.custom-checkbox');
$(this).prop('checked', shouldSelectAll);
if (shouldSelectAll) {
if (!selectedQuestions.includes(questionIndex)) {
selectedQuestions.push(questionIndex);
}
questionContainer.addClass('selected');
checkbox.addClass('checked');
checkbox.find('i').show();
} else {
selectedQuestions = [];
questionContainer.removeClass('selected');
checkbox.removeClass('checked');
checkbox.find('i').hide();
}
});
updateSelectionUI();
});
// Deselect all button
$('#deselectAll').click(function() {
selectedQuestions = [];
$('.question-checkbox-input').prop('checked', false);
$('.question').removeClass('selected');
$('.custom-checkbox').removeClass('checked');
$('.custom-checkbox i').hide();
updateSelectionUI();
});
// Handle bulk delete button
$('#deleteSelectedQuestions').click(function() {
if (selectedQuestions.length === 0) {
alert('Please select at least one question to delete.');
return;
}
// Update modal content
$('#deletionCount, #questionCount').text(selectedQuestions.length);
// Populate preview
const previewContainer = $('#deletionPreview .preview-list');
previewContainer.empty();
selectedQuestions.forEach(function(questionIndex) {
const questionContainer = $(`.question[data-question-index="${questionIndex}"]`);
const questionText = questionContainer.find('.item-question').text().trim();
const truncatedText = questionText.length > 80 ? questionText.substring(0, 80) + '...' : questionText;
previewContainer.append(`
<div class="mb-2 p-2 bg-white rounded border">
<strong>Question ${questionIndex + 1}:</strong> ${truncatedText}
</div>
`);
});
$('#confirmBulkDeleteModal').modal('show');
});
// Confirm bulk deletion
$('#confirmBulkDelete').click(function() {
const $btn = $(this);
$btn.addClass('loading').prop('disabled', true);
// Sort in descending order to avoid index issues when removing
selectedQuestions.sort((a, b) => b - a);
// Remove selected questions with animation
let delay = 0;
selectedQuestions.forEach(function(questionIndex) {
setTimeout(function() {
const questionContainer = $(`.question[data-question-index="${questionIndex}"]`);
questionContainer.fadeOut(300, function() {
$(this).remove();
});
}, delay);
delay += 100;
});
// Reset selection state and re-index form after all deletions
setTimeout(function() {
selectedQuestions = [];
updateSelectionUI();
// Re-index all remaining questions and their form fields
reindexAllQuestions();
$('#confirmBulkDeleteModal').modal('hide');
$btn.removeClass('loading').prop('disabled', false);
// Show success message
const successMessage = $('<div class="alert alert-success fade-in" role="alert"><i class="bi bi-check-circle me-2"></i>Selected questions deleted successfully!</div>');
$('#customCard').prepend(successMessage);
setTimeout(function() {
successMessage.fadeOut(300, function() {
$(this).remove();
});
}, 3000);
}, delay + 500);
});
// Function to update question numbers after deletion
function updateQuestionNumbers() {
$('.question').each(function(index) {
$(this).find('.badge').text('Question ' + (index + 1));
$(this).attr('data-question-index', index);
});
}
// Function to properly re-index all form fields after question deletion
function reindexAllQuestions() {
$('.question').each(function(questionIndex) {
const questionContainer = $(this);
// Update data attribute and badge
questionContainer.attr('data-question-index', questionIndex);
questionContainer.find('.badge').text('Question ' + (questionIndex + 1));
// Update question form fields
questionContainer.find('input[name*="Questions["]').each(function() {
const name = $(this).attr('name');
if (name) {
const newName = name.replace(/Questions\[\d+\]/, `Questions[${questionIndex}]`);
$(this).attr('name', newName);
}
});
questionContainer.find('select[name*="Questions["]').each(function() {
const name = $(this).attr('name');
if (name) {
const newName = name.replace(/Questions\[\d+\]/, `Questions[${questionIndex}]`);
$(this).attr('name', newName);
}
});
questionContainer.find('textarea[name*="Questions["]').each(function() {
const name = $(this).attr('name');
if (name) {
const newName = name.replace(/Questions\[\d+\]/, `Questions[${questionIndex}]`);
$(this).attr('name', newName);
}
});
// Update answer form fields
questionContainer.find('.answer-group').each(function(answerIndex) {
$(this).find('input[name*="Answers["]').each(function() {
const name = $(this).attr('name');
if (name) {
const newName = name.replace(/Questions\[\d+\]\.Answers\[\d+\]/, `Questions[${questionIndex}].Answers[${answerIndex}]`);
$(this).attr('name', newName);
}
});
// Update answer labels
const label = $(this).find('label.control-label').first();
if (label.length) {
const labelText = label.text();
if (labelText.includes('Answer ')) {
const otherContent = label.find('.other-option-badge').length > 0 ?
' <span class="other-option-badge">Other Option</span>' : '';
label.html(`Answer ${answerIndex + 1}${otherContent}`);
}
}
});
});
}
// Function to add a new regular answer // Function to add a new regular answer
$(document).on('click', '.addAnswer', function () { $(document).on('click', '.addAnswer', function () {
var questionContainer = $(this).closest('.question'); var questionContainer = $(this).closest('.question');
var newQuestionIndex = questionContainer.index(); var questionIndex = questionContainer.attr('data-question-index');
var newAnswerIndex = questionContainer.find('.answers .answer-group').length; var newAnswerIndex = questionContainer.find('.answers .answer-group').length;
var answerHtml = ` var answerHtml = `
@ -891,8 +1347,8 @@
<div class="form-group mb-0"> <div class="form-group mb-0">
<label class="control-label">Answer ${newAnswerIndex + 1}</label> <label class="control-label">Answer ${newAnswerIndex + 1}</label>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<input type="text" name="Questions[${newQuestionIndex}].Answers[${newAnswerIndex}].Text" class="form-control" placeholder="Enter answer text" /> <input type="text" name="Questions[${questionIndex}].Answers[${newAnswerIndex}].Text" class="form-control" placeholder="Enter answer text" />
<input type="hidden" name="Questions[${newQuestionIndex}].Answers[${newAnswerIndex}].IsOtherOption" value="false" /> <input type="hidden" name="Questions[${questionIndex}].Answers[${newAnswerIndex}].IsOtherOption" value="false" />
<button type="button" class="btn btn-danger btn-sm removeAnswer"> <button type="button" class="btn btn-danger btn-sm removeAnswer">
<i class="bi bi-trash3"></i> <i class="bi bi-trash3"></i>
</button> </button>
@ -906,7 +1362,7 @@
// Function to add "Other" option // Function to add "Other" option
$(document).on('click', '.addOtherOption', function () { $(document).on('click', '.addOtherOption', function () {
var questionContainer = $(this).closest('.question'); var questionContainer = $(this).closest('.question');
var newQuestionIndex = questionContainer.index(); var questionIndex = questionContainer.attr('data-question-index');
var newAnswerIndex = questionContainer.find('.answers .answer-group').length; var newAnswerIndex = questionContainer.find('.answers .answer-group').length;
// Check if "Other" option already exists // Check if "Other" option already exists
@ -924,8 +1380,8 @@
<span class="other-option-badge">Other Option</span> <span class="other-option-badge">Other Option</span>
</label> </label>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<input type="text" name="Questions[${newQuestionIndex}].Answers[${newAnswerIndex}].Text" class="form-control" placeholder="Other (please specify)" value="Other (please specify)" /> <input type="text" name="Questions[${questionIndex}].Answers[${newAnswerIndex}].Text" class="form-control" placeholder="Other (please specify)" value="Other (please specify)" />
<input type="hidden" name="Questions[${newQuestionIndex}].Answers[${newAnswerIndex}].IsOtherOption" value="true" /> <input type="hidden" name="Questions[${questionIndex}].Answers[${newAnswerIndex}].IsOtherOption" value="true" />
<button type="button" class="btn btn-warning btn-sm removeAnswer"> <button type="button" class="btn btn-warning btn-sm removeAnswer">
<i class="bi bi-trash3"></i> Remove Other <i class="bi bi-trash3"></i> Remove Other
</button> </button>
@ -942,8 +1398,28 @@
// Function to remove an answer // Function to remove an answer
$(document).on('click', '.removeAnswer', function () { $(document).on('click', '.removeAnswer', function () {
const questionContainer = $(this).closest('.question');
$(this).closest('.answer-group').fadeOut(300, function() { $(this).closest('.answer-group').fadeOut(300, function() {
$(this).remove(); $(this).remove();
// Re-index answers in this question
const questionIndex = questionContainer.attr('data-question-index');
questionContainer.find('.answer-group').each(function(answerIndex) {
$(this).find('input[name*="Answers["]').each(function() {
const name = $(this).attr('name');
if (name) {
const newName = name.replace(/Questions\[\d+\]\.Answers\[\d+\]/, `Questions[${questionIndex}].Answers[${answerIndex}]`);
$(this).attr('name', newName);
}
});
// Update answer labels
const label = $(this).find('label.control-label').first();
if (label.length) {
const otherContent = label.find('.other-option-badge').length > 0 ?
' <span class="other-option-badge">Other Option</span>' : '';
label.html(`Answer ${answerIndex + 1}${otherContent}`);
}
});
}); });
}); });
@ -1002,8 +1478,16 @@
// Confirm deletion // Confirm deletion
$('.confirm-delete').click(function () { $('.confirm-delete').click(function () {
if (questionToDelete) { if (questionToDelete) {
const questionIndex = questionToDelete.data('question-index');
// Remove from selection if it was selected
selectedQuestions = selectedQuestions.filter(q => q !== questionIndex);
updateSelectionUI();
questionToDelete.fadeOut(300, function() { questionToDelete.fadeOut(300, function() {
$(this).remove(); $(this).remove();
// Re-index all remaining questions and their form fields
reindexAllQuestions();
}); });
$('#confirmDeleteModal').modal('hide'); $('#confirmDeleteModal').modal('hide');
questionToDelete = null; questionToDelete = null;
@ -1019,7 +1503,15 @@
var newQuestionIndex = $('.question').length; var newQuestionIndex = $('.question').length;
var questionHtml = ` var questionHtml = `
<div class="question fade-in"> <div class="question fade-in" data-question-index="${newQuestionIndex}">
<!-- Question Checkbox -->
<div class="question-checkbox">
<div class="custom-checkbox question-select-checkbox">
<input type="checkbox" class="question-checkbox-input">
<i class="bi bi-check" style="display: none;"></i>
</div>
</div>
<div class="question-separator"> <div class="question-separator">
<div class="line-container"> <div class="line-container">
<hr class="line-start"> <hr class="line-start">
@ -1101,6 +1593,9 @@
$btn.removeClass('loading').prop('disabled', false); $btn.removeClass('loading').prop('disabled', false);
}, 300); }, 300);
}); });
// Initialize UI
updateSelectionUI();
}); });
</script> </script>
} }

View file

@ -1,97 +1,836 @@
@model IEnumerable<QuestionnaireViewModel> @model IEnumerable<QuestionnaireViewModel>
@{ @{
ViewData["Title"] = "Questionnaire"; ViewData["Title"] = "Questionnaire Management";
} }
<style>
/* Modern Design System */
@@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
<div class="container-fluid mt-3"> :root {
--primary-color: #6366f1;
--primary-light: #8b5cf6;
--primary-dark: #4338ca;
--success-color: #10b981;
--success-light: #34d399;
--success-dark: #059669;
--info-color: #06b6d4;
--info-light: #22d3ee;
--info-dark: #0891b2;
--warning-color: #f59e0b;
--warning-light: #fbbf24;
--warning-dark: #d97706;
--danger-color: #ef4444;
--danger-light: #fca5a5;
--danger-dark: #dc2626;
--purple-color: #6f42c1;
--purple-light: #8b5cf6;
--purple-dark: #5a2d91;
--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: 24px;
}
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
/* Header Section */
.page-header {
background: white;
border-radius: var(--border-radius-xl);
box-shadow: var(--shadow-xl);
padding: 2.5rem;
margin-bottom: 2rem;
position: relative;
overflow: hidden;
}
.page-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--primary-color), var(--primary-light), var(--info-color), var(--success-color));
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 1.5rem;
}
.header-info {
display: flex;
align-items: center;
gap: 1.5rem;
}
.header-icon {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
color: white;
width: 64px;
height: 64px;
border-radius: var(--border-radius-lg);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.75rem;
box-shadow: var(--shadow-lg);
}
.header-text h1 {
font-size: 2.25rem;
font-weight: 800;
color: var(--gray-800);
margin: 0 0 0.5rem;
}
.header-subtitle {
color: var(--gray-600);
font-size: 1.1rem;
font-weight: 500;
}
.create-button {
background: linear-gradient(135deg, var(--success-color) 0%, var(--success-dark) 100%);
color: white;
border: none;
padding: 1rem 2rem;
border-radius: var(--border-radius-md);
font-weight: 600;
font-size: 1rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.75rem;
transition: all 0.3s ease;
box-shadow: var(--shadow-lg);
position: relative;
overflow: hidden;
}
.create-button::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;
}
.create-button:hover::before {
left: 100%;
}
.create-button:hover {
background: linear-gradient(135deg, var(--success-dark) 0%, #047857 100%);
transform: translateY(-2px);
box-shadow: var(--shadow-xl);
color: white;
text-decoration: none;
}
/* Stats Section */
.stats-section {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
color: white;
padding: 2rem;
margin-bottom: 2rem;
border-radius: var(--border-radius-lg);
box-shadow: var(--shadow-lg);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1.5rem;
text-align: center;
}
.stat-item {
padding: 1rem;
background: rgba(255, 255, 255, 0.1);
border-radius: var(--border-radius-md);
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.stat-item:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
}
.stat-number {
font-size: 2.5rem;
font-weight: 800;
display: block;
margin-bottom: 0.25rem;
}
.stat-label {
font-size: 0.875rem;
font-weight: 500;
opacity: 0.9;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Questionnaire Grid */
.questionnaires-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
/* Questionnaire Card */
.questionnaire-card {
background: white;
border-radius: var(--border-radius-xl);
box-shadow: var(--shadow-lg);
overflow: hidden;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
border: 1px solid var(--gray-200);
}
.questionnaire-card:hover {
transform: translateY(-8px);
box-shadow: var(--shadow-2xl);
border-color: var(--primary-color);
}
.questionnaire-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--primary-color), var(--info-color), var(--success-color));
transform: scaleX(0);
transition: transform 0.3s ease;
transform-origin: left;
}
.questionnaire-card:hover::before {
transform: scaleX(1);
}
/* Card Header */
.card-header-custom {
background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%);
padding: 1.5rem;
border-bottom: 1px solid var(--gray-200);
}
.card-id-badge {
background: linear-gradient(135deg, var(--warning-color) 0%, var(--warning-dark) 100%);
color: white;
padding: 0.375rem 0.75rem;
border-radius: var(--border-radius-sm);
font-weight: 600;
font-size: 0.875rem;
float: right;
box-shadow: var(--shadow-sm);
}
.card-title-custom {
font-size: 1.25rem;
font-weight: 700;
color: var(--gray-800);
margin: 0;
line-height: 1.4;
word-break: break-word;
padding-right: 5rem;
}
/* Card Content */
.card-content {
padding: 1.5rem;
}
.questions-count-section {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
color: white;
padding: 1rem;
border-radius: var(--border-radius-md);
margin-bottom: 1.5rem;
text-align: center;
}
.questions-count-number {
font-size: 2rem;
font-weight: 800;
display: block;
margin-bottom: 0.25rem;
}
.questions-count-label {
font-size: 0.875rem;
font-weight: 500;
opacity: 0.9;
}
/* Preview Section */
.preview-section {
background: var(--gray-50);
border-radius: var(--border-radius-md);
padding: 1.25rem;
margin-bottom: 1.5rem;
border: 1px solid var(--gray-200);
}
.preview-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
.preview-icon {
background: var(--info-color);
color: white;
width: 24px;
height: 24px;
border-radius: var(--border-radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
}
.preview-title {
font-size: 0.875rem;
font-weight: 600;
color: var(--gray-700);
margin: 0;
}
.preview-content {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.preview-question {
background: white;
padding: 1rem;
border-radius: var(--border-radius-sm);
border: 1px solid var(--gray-200);
box-shadow: var(--shadow-sm);
}
.question-badge {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
color: white;
padding: 0.25rem 0.75rem;
border-radius: var(--border-radius-sm);
font-size: 0.75rem;
font-weight: 600;
margin-bottom: 0.5rem;
display: inline-block;
}
.type-badge {
background: linear-gradient(135deg, var(--info-color) 0%, var(--info-dark) 100%);
color: white;
padding: 0.25rem 0.75rem;
border-radius: var(--border-radius-sm);
font-size: 0.75rem;
font-weight: 600;
margin-left: 0.5rem;
display: inline-block;
}
.question-text {
font-size: 0.875rem;
color: var(--gray-700);
margin: 0.5rem 0;
font-weight: 500;
line-height: 1.4;
}
.answers-container {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.75rem;
}
.answer-badge {
background: linear-gradient(135deg, var(--success-color) 0%, var(--success-dark) 100%);
color: white;
padding: 0.25rem 0.75rem;
border-radius: var(--border-radius-sm);
font-size: 0.75rem;
font-weight: 500;
box-shadow: var(--shadow-sm);
}
.more-questions-indicator {
background: var(--gray-200);
color: var(--gray-600);
padding: 0.75rem;
border-radius: var(--border-radius-sm);
text-align: center;
font-size: 0.875rem;
font-weight: 500;
font-style: italic;
border: 1px dashed var(--gray-300);
}
/* Action Buttons */
.action-buttons {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
padding: 1.5rem;
background: var(--gray-50);
border-top: 1px solid var(--gray-200);
}
.action-btn {
padding: 0.75rem 1rem;
border-radius: var(--border-radius-md);
font-weight: 600;
font-size: 0.875rem;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
transition: all 0.3s ease;
border: none;
box-shadow: var(--shadow-sm);
position: relative;
overflow: hidden;
}
.action-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;
}
.action-btn:hover::before {
left: 100%;
}
.action-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
text-decoration: none;
}
.btn-delete {
background: linear-gradient(135deg, var(--danger-color) 0%, var(--danger-dark) 100%);
color: white;
}
.btn-delete:hover {
background: linear-gradient(135deg, var(--danger-dark) 0%, #b91c1c 100%);
color: white;
}
.btn-edit {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
color: white;
}
.btn-edit:hover {
background: linear-gradient(135deg, var(--primary-dark) 0%, #3730a3 100%);
color: white;
}
.btn-details {
background: linear-gradient(135deg, var(--info-color) 0%, var(--info-dark) 100%);
color: white;
}
.btn-details:hover {
background: linear-gradient(135deg, var(--info-dark) 0%, #0e7490 100%);
color: white;
}
.btn-send {
background: linear-gradient(135deg, var(--success-color) 0%, var(--success-dark) 100%);
color: white;
}
.btn-send:hover {
background: linear-gradient(135deg, var(--success-dark) 0%, #047857 100%);
color: white;
}
.btn-logic {
background: linear-gradient(135deg, var(--purple-color) 0%, var(--purple-dark) 100%);
color: white;
grid-column: 1 / -1;
}
.btn-logic:hover {
background: linear-gradient(135deg, var(--purple-dark) 0%, #4c1d95 100%);
color: white;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 4rem 2rem;
background: white;
border-radius: var(--border-radius-xl);
box-shadow: var(--shadow-lg);
border: 2px dashed var(--gray-300);
}
.empty-state-icon {
background: var(--gray-100);
color: var(--gray-400);
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
margin: 0 auto 1.5rem;
}
.empty-state h3 {
font-size: 1.5rem;
font-weight: 700;
color: var(--gray-700);
margin-bottom: 1rem;
}
.empty-state p {
color: var(--gray-500);
font-size: 1rem;
margin-bottom: 2rem;
}
/* Responsive Design */
@@media (max-width: 1024px) {
.questionnaires-grid
{
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
}
}
@@media (max-width: 768px) {
.container-fluid
{
padding: 1rem;
}
.header-content {
flex-direction: column;
align-items: flex-start;
}
.header-info {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.questionnaires-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.action-buttons {
grid-template-columns: 1fr;
}
.btn-logic {
grid-column: 1;
}
}
/* Animations */
.fade-in {
animation: fadeIn 0.6s ease-out;
}
@@keyframes fadeIn {
from
{
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-in {
animation: slideIn 0.4s ease-out;
}
@@keyframes slideIn {
from
{
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.stagger-animation {
animation: fadeIn 0.6s ease-out;
animation-fill-mode: both;
}
.stagger-animation:nth-child(1) {
animation-delay: 0.1s;
}
.stagger-animation:nth-child(2) {
animation-delay: 0.2s;
}
.stagger-animation:nth-child(3) {
animation-delay: 0.3s;
}
.stagger-animation:nth-child(4) {
animation-delay: 0.4s;
}
.stagger-animation:nth-child(5) {
animation-delay: 0.5s;
}
.stagger-animation:nth-child(6) {
animation-delay: 0.6s;
}
</style>
<div class="container-fluid">
<!-- Notifications -->
<partial name="_Notification" /> <partial name="_Notification" />
<div class="card bg-default shadow "> <!-- Page Header -->
<div class="card-header">Questionnaire</div> <div class="page-header fade-in">
<div class="card-body"> <div class="header-content">
<h4 class="card-title">Questionnaire list</h4> <div class="header-info">
<p> <div class="header-icon">
<a asp-action="Create" class="btn btn-primary"><span><i class="bi bi-plus-square-fill"></i></span> Create New</a> <i class="bi bi-clipboard-data-fill"></i>
</p> </div>
<table class="table table-responsive w-100 d-block d-md-table table-hover"> <div class="header-text">
<thead > <h1>Questionnaire Management</h1>
<tr> <p class="header-subtitle">Create, manage, and analyze your questionnaires</p>
</div>
<th>Id</th> </div>
<th>Title</th> <a asp-action="Create" class="create-button">
<th>Total Questions</th> <i class="bi bi-plus-circle-fill"></i>
<th> <span class="badge badge-primary">Questions</span> | <span class="badge badge-info">Type</span> | <span class="badge badge-success">Answers </span></th> Create New Questionnaire
</a>
<th>Action</th>
</tr>
</thead>
<tbody class="w-100">
@foreach (var item in Model)
{
<tr class="table-secondary">
<td>@item.Id</td>
<td>
@item.Title
</td>
<td>
<span class="badge shadow rounded text-bg-primary p-2">
Questions <span class="badge text-bg-secondary shadow rounded p-1">@item.Questions?.Count()</span>
</span>
</td>
<td class="h5">
@foreach (var question in item.Questions.Take(1))
{
<span class="badge p-1 m-1 bg-primary shadow-sm"> Question:@question.Text</span>
<span class="badge p-1 m-1 bg-info shadow-sm">Type: @question.Type</span>
foreach (var answer in question.Answers)
{
<span class="badge p-1 m-1 bg-success shadow-sm"> Asnwer:@answer.Text</span>
}
}
</td>
<td>
<!-- Your existing buttons -->
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-danger btn-sm">
🗑️ DELETE
</a>
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-primary btn-sm">
✏️ EDIT
</a>
<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-info btn-sm">
👁️ DETAILS
</a>
<a asp-action="SendQuestionnaire" asp-route-id="@item.Id" class="btn btn-success btn-sm">
📧 SEND
</a>
<!-- NEW: SET LOGIC button with purple/orange styling -->
<a asp-action="SetLogic" asp-route-id="@item.Id" class="btn btn-sm" style="background-color: #6f42c1; color: white;">
🔀 SET LOGIC
</a>
</td>
</tr>
}
</tbody>
</table>
</div> </div>
</div> </div>
</div>
<!-- Statistics Section -->
@if (Model.Any())
{
<div class="stats-section fade-in">
<div class="stats-grid">
<div class="stat-item">
<span class="stat-number">@Model.Count()</span>
<span class="stat-label">Total Questionnaires</span>
</div>
<div class="stat-item">
<span class="stat-number">@Model.Sum(q => q.Questions?.Count ?? 0)</span>
<span class="stat-label">Total Questions</span>
</div>
<div class="stat-item">
<span class="stat-number">@Model.Sum(q => q.Questions?.Sum(x => x.Answers?.Count ?? 0) ?? 0)</span>
<span class="stat-label">Total Answers</span>
</div>
<div class="stat-item">
<span class="stat-number">@Model.SelectMany(q => q.Questions ?? new List<Question>()).Select(q => q.Type).Distinct().Count()</span>
<span class="stat-label">Question Types</span>
</div>
</div>
</div>
}
<!-- Questionnaires Grid -->
@if (Model.Any())
{
<div class="questionnaires-grid">
@foreach (var item in Model.Select((questionnaire, index) => new { Questionnaire = questionnaire, Index = index }))
{
<div class="questionnaire-card stagger-animation">
<!-- Card Header -->
<div class="card-header-custom">
<span class="card-id-badge">ID: @item.Questionnaire.Id</span>
<h3 class="card-title-custom">@item.Questionnaire.Title</h3>
</div>
<!-- Card Content -->
<div class="card-content">
<!-- Questions Count -->
<div class="questions-count-section">
<span class="questions-count-number">@(item.Questionnaire.Questions?.Count ?? 0)</span>
<span class="questions-count-label">Question@(item.Questionnaire.Questions?.Count != 1 ? "s" : "")</span>
</div>
<!-- Preview Section -->
@if (item.Questionnaire.Questions?.Any() == true)
{
<div class="preview-section">
<div class="preview-header">
<div class="preview-icon">
<i class="bi bi-eye"></i>
</div>
<h4 class="preview-title">Quick Preview</h4>
</div>
<div class="preview-content">
@{
var firstQuestion = item.Questionnaire.Questions.FirstOrDefault();
var totalQuestions = item.Questionnaire.Questions.Count;
}
@if (firstQuestion != null)
{
<div class="preview-question">
<div>
<span class="question-badge">Question: @firstQuestion.Text</span>
<span class="type-badge">Type: @firstQuestion.Type</span>
</div>
@if (firstQuestion.Answers?.Any() == true)
{
<div class="answers-container">
@foreach (var answer in firstQuestion.Answers.Take(3))
{
<span class="answer-badge">@answer.Text</span>
}
@if (firstQuestion.Answers.Count > 3)
{
<span class="answer-badge" style="background: var(--gray-400);">+@(firstQuestion.Answers.Count - 3) more</span>
}
</div>
}
</div>
}
@if (totalQuestions > 1)
{
<div class="more-questions-indicator">
+ @(totalQuestions - 1) more question@(totalQuestions - 1 != 1 ? "s" : "")
</div>
}
</div>
</div>
}
else
{
<div class="preview-section">
<div class="preview-header">
<div class="preview-icon">
<i class="bi bi-info-circle"></i>
</div>
<h4 class="preview-title">No Questions Yet</h4>
</div>
<p style="color: var(--gray-500); font-style: italic; margin: 0;">This questionnaire doesn't have any questions yet.</p>
</div>
}
</div>
<!-- Action Buttons -->
<div class="action-buttons">
<a asp-action="Edit" asp-route-id="@item.Questionnaire.Id" class="action-btn btn-edit">
<i class="bi bi-pencil-fill"></i>
Edit
</a>
<a asp-action="Details" asp-route-id="@item.Questionnaire.Id" class="action-btn btn-details">
<i class="bi bi-eye-fill"></i>
Details
</a>
<a asp-action="Delete" asp-route-id="@item.Questionnaire.Id" class="action-btn btn-delete">
<i class="bi bi-trash3-fill"></i>
Delete
</a>
<a asp-action="SendQuestionnaire" asp-route-id="@item.Questionnaire.Id" class="action-btn btn-send">
<i class="bi bi-envelope-fill"></i>
Send
</a>
<a asp-action="SetLogic" asp-route-id="@item.Questionnaire.Id" class="action-btn btn-logic">
<i class="bi bi-diagram-3-fill"></i>
Set Logic
</a>
</div>
</div>
}
</div>
}
else
{
<!-- Empty State -->
<div class="empty-state fade-in">
<div class="empty-state-icon">
<i class="bi bi-clipboard-x"></i>
</div>
<h3>No Questionnaires Yet</h3>
<p>Get started by creating your first questionnaire to collect valuable insights.</p>
<a asp-action="Create" class="create-button">
<i class="bi bi-plus-circle-fill"></i>
Create Your First Questionnaire
</a>
</div>
}
</div>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -10,5 +10,6 @@ namespace Web.ViewModel.AnswerVM
public int QuestionId { get; set; } // Foreign key for Question public int QuestionId { get; set; } // Foreign key for Question
[ForeignKey("QuestionId")] [ForeignKey("QuestionId")]
public Question? Question { get; set; } public Question? Question { get; set; }
public bool IsDeleted { get; set; } = false;
} }
} }