SurveyVista/Web/Areas/Admin/Views/Questionnaire/SetLogic.cshtml

1281 lines
No EOL
50 KiB
Text

@model SetLogicViewModel
@{
ViewData["Title"] = "Set Conditional Logic";
}
<style>
* {
box-sizing: border-box;
}
.conditional-logic-page {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
color: #2c3e50;
padding: 20px;
}
.main-container {
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
overflow: hidden;
max-width: 1400px;
margin: 0 auto;
}
.header {
background: linear-gradient(135deg, #6c5ce7 0%, #a29bfe 100%);
color: white;
padding: 30px 40px;
position: relative;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 20px;
}
.header-left h1 {
font-size: 2.2rem;
font-weight: 600;
margin: 0 0 8px 0;
}
.header-left p {
font-size: 1rem;
opacity: 0.9;
margin: 0;
}
.header-stats {
text-align: right;
}
.stats-number {
font-size: 2.5rem;
font-weight: 700;
line-height: 1;
display: block;
}
.stats-label {
font-size: 0.9rem;
opacity: 0.9;
}
.page-content {
padding: 30px 40px;
}
.alert {
padding: 15px 20px;
margin-bottom: 25px;
border-radius: 8px;
border: none;
font-weight: 500;
}
.alert-success {
background: #d4edda;
color: #155724;
border-left: 4px solid #28a745;
}
.alert-danger {
background: #f8d7da;
color: #721c24;
border-left: 4px solid #dc3545;
}
.overview-section {
background: #f8f9fa;
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
border: 1px solid #e9ecef;
}
.overview-title {
font-size: 1.5rem;
font-weight: 600;
color: #495057;
margin-bottom: 25px;
text-align: center;
}
.questions-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 15px;
}
.question-overview-card {
background: white;
border-radius: 8px;
padding: 20px;
border: 1px solid #dee2e6;
transition: all 0.2s ease;
position: relative;
}
.question-overview-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border-color: #6c5ce7;
}
.question-number {
font-weight: 700;
color: #6c5ce7;
font-size: 1.1rem;
margin-bottom: 8px;
}
.question-preview {
font-size: 0.9rem;
color: #6c757d;
line-height: 1.4;
}
.logic-badge {
position: absolute;
top: 15px;
right: 15px;
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.questions-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-top: 30px;
}
.question-card {
background: white;
border-radius: 12px;
border: 1px solid #e9ecef;
overflow: hidden;
transition: all 0.2s ease;
}
.question-card:hover {
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.question-header {
background: #f8f9fa;
padding: 20px 25px;
border-bottom: 1px solid #e9ecef;
position: relative;
}
.question-header.has-logic {
background: linear-gradient(135deg, #ffe8e8, #fff0f0);
border-bottom-color: #ff6b6b;
}
.question-header.has-logic::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #ff6b6b, #ee5a24);
}
.question-title {
font-size: 1.2rem;
font-weight: 600;
margin: 0 0 8px 0;
color: #2c3e50;
line-height: 1.3;
}
.question-meta {
font-size: 0.9rem;
color: #6c757d;
margin: 0;
}
.question-controls {
margin-top: 15px;
}
.btn-toggle {
background: #6c5ce7;
border: none;
color: white;
padding: 8px 16px;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-toggle:hover {
background: #5f4fcf;
transform: translateY(-1px);
}
.btn-toggle.hide-btn {
background: #6c757d;
}
.btn-toggle.hide-btn:hover {
background: #545b62;
}
.question-content {
padding: 25px;
display: none;
}
.question-content.show {
display: block;
animation: fadeIn 0.3s ease;
}
@@keyframes fadeIn {
from
{
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.answers-grid {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
grid-auto-rows: auto;
align-items: start;
}
/* For questions with multiple answers, display in 2 columns on larger screens */
.answers-grid.multi-answers {
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
}
.answer-card {
background: #fdfdfd;
border: 2px solid #e9ecef;
border-radius: 10px;
overflow: hidden;
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
height: auto;
min-height: auto;
}
.answer-card.has-condition {
border-color: #ff6b6b;
background: linear-gradient(135deg, #fff8f8, #fff0f0);
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.1);
}
.answer-card:hover {
border-color: #6c5ce7;
box-shadow: 0 6px 20px rgba(108, 92, 231, 0.15);
transform: translateY(-2px);
}
.answer-header {
background: #e9ecef;
padding: 15px 20px;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: space-between;
align-items: center;
}
.answer-card.has-condition .answer-header {
background: #ff6b6b;
color: white;
}
.answer-title {
font-weight: 600;
margin: 0;
}
.active-badge {
background: white;
color: #ff6b6b;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.answer-content {
padding: 20px;
}
.answer-actions {
padding: 15px 20px;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
display: flex;
gap: 10px;
justify-content: flex-end;
align-items: center;
}
.btn-save-answer {
background: #28a745;
border: none;
color: white;
padding: 8px 16px;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-save-answer:hover {
background: #218838;
transform: translateY(-1px);
}
.btn-reset-answer {
background: #dc3545;
border: none;
color: white;
padding: 8px 16px;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-reset-answer:hover {
background: #c82333;
transform: translateY(-1px);
}
.answer-status {
font-size: 0.8rem;
padding: 4px 8px;
border-radius: 4px;
font-weight: 500;
margin-right: auto;
}
.answer-status.saved {
background: #d4edda;
color: #155724;
}
.answer-status.modified {
background: #fff3cd;
color: #856404;
}
.answer-text {
font-weight: 600;
color: #495057;
font-size: 1rem;
margin: 0 0 20px 0;
padding: 12px;
background: white;
border-radius: 6px;
border-left: 4px solid #6c5ce7;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
font-weight: 600;
color: #495057;
margin-bottom: 8px;
display: block;
font-size: 0.9rem;
}
.form-control {
border: 2px solid #e9ecef;
border-radius: 6px;
padding: 10px 15px;
font-size: 0.9rem;
transition: all 0.2s ease;
width: 100%;
height: auto;
}
.form-control:focus {
border-color: #6c5ce7;
box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.1);
outline: none;
}
.condition-option {
margin-bottom: 15px;
}
.condition-summary {
background: linear-gradient(135deg, #e3f2fd, #f3e5f5);
color: #1565c0;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
font-weight: 500;
border-left: 4px solid #2196f3;
}
.save-section {
background: #f8f9fa;
padding: 30px;
margin: 30px 0 0 0;
border-radius: 12px;
text-align: center;
border-top: 1px solid #e9ecef;
}
.btn-primary {
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
border: none;
color: white;
padding: 12px 24px;
margin: 8px;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
display: inline-block;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(108, 92, 231, 0.3);
color: white;
text-decoration: none;
}
.btn-secondary {
background: #6c757d;
border: none;
color: white;
padding: 12px 24px;
margin: 8px;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
display: inline-block;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-secondary:hover {
background: #545b62;
transform: translateY(-2px);
color: white;
text-decoration: none;
}
.d-none {
display: none !important;
}
.small-text {
font-size: 0.8rem;
color: #6c757d;
margin-top: 5px;
}
@@media (max-width: 768px) {
.questions-container
{
grid-template-columns: 1fr;
gap: 20px;
}
.header-content {
text-align: center;
}
.header-left h1 {
font-size: 1.8rem;
}
.page-content {
padding: 20px;
}
.questions-grid {
grid-template-columns: 1fr;
}
/* Always single column for answers on mobile */
.answers-grid.multi-answers {
grid-template-columns: 1fr;
}
}
</style>
<div class="conditional-logic-page">
<div class="main-container">
<!-- Header -->
<div class="header">
<div class="header-content">
<div class="header-left">
<h1>🔮 Conditional Logic Manager</h1>
<p>Configure smart question routing for: <strong>@Model.QuestionnaireName</strong></p>
</div>
<div class="header-stats">
<span class="stats-number">@Model.Questions.Count</span>
<div class="stats-label">Questions</div>
</div>
</div>
</div>
<div class="page-content">
@if (TempData["Success"] != null)
{
<div class="alert alert-success">
<i class="fas fa-check-circle"></i> @TempData["Success"]
</div>
}
@if (TempData["Error"] != null)
{
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle"></i> @TempData["Error"]
</div>
}
<!-- Overview Section -->
<div class="overview-section">
<h2 class="overview-title">📋 Survey Structure Overview</h2>
<div class="questions-grid">
@for (int i = 0; i < Model.Questions.Count; i++)
{
var hasConditions = Model.Questions[i].Answers.Any(a => a.ActionType != ConditionActionType.Continue);
<div class="question-overview-card">
@if (hasConditions)
{
<div class="logic-badge">🔥 Logic</div>
}
<div class="question-number">Q@(i + 1)</div>
<div class="question-preview">
@(Model.Questions[i].QuestionText.Length > 60
? Model.Questions[i].QuestionText.Substring(0, 60) + "..."
: Model.Questions[i].QuestionText)
</div>
</div>
}
</div>
</div>
<form id="logicForm" method="post" asp-action="SaveLogic" novalidate>
<input type="hidden" name="QuestionnaireId" value="@Model.QuestionnaireId" />
<!-- Questions in 2-Column Layout -->
<div class="questions-container">
@for (int questionIndex = 0; questionIndex < Model.Questions.Count; questionIndex++)
{
var question = Model.Questions[questionIndex];
var hasConditions = question.Answers.Any(a => a.ActionType != ConditionActionType.Continue);
<div class="question-card">
<div class="question-header @(hasConditions ? "has-logic" : "")">
<h3 class="question-title">
Question @(questionIndex + 1): @question.QuestionText
</h3>
<p class="question-meta">
@question.QuestionType • @question.Answers.Count answers
@if (hasConditions)
{
<span style="margin-left: 10px; color: #ff6b6b; font-weight: 600;">
<i class="fas fa-magic"></i> Has Logic Rules
</span>
}
</p>
<div class="question-controls">
<button type="button" class="btn-toggle show-btn" onclick="showQuestion(@questionIndex)">
<i class="fas fa-eye"></i> Show Details
</button>
<button type="button" class="btn-toggle hide-btn d-none" onclick="hideQuestion(@questionIndex)">
<i class="fas fa-eye-slash"></i> Hide Details
</button>
</div>
</div>
<div id="question-content-@questionIndex" class="question-content">
@if (question.Answers.Any())
{
<div class="answers-grid">
@for (int answerIndex = 0; answerIndex < question.Answers.Count; answerIndex++)
{
var answer = question.Answers[answerIndex];
var hasCondition = answer.ActionType != ConditionActionType.Continue;
<div class="answer-card @(hasCondition ? "has-condition" : "")">
<div class="answer-header">
<h4 class="answer-title">📝 Answer @(answerIndex + 1)</h4>
@if (hasCondition)
{
<span class="active-badge">
<i class="fas fa-cog"></i> Active
</span>
}
</div>
<div class="answer-content">
<div class="answer-text">@answer.AnswerText</div>
<!-- Action Type Selection -->
<div class="form-group">
<label class="form-label">When this answer is selected:</label>
<select class="form-control action-type-select"
data-answer-id="@answer.AnswerId"
onchange="toggleConditionOptions(this)">
@if (answer.ActionType == ConditionActionType.Continue)
{
<option value="0" selected>➡️ Continue to next question</option>
}
else
{
<option value="0">➡️ Continue to next question</option>
}
@if (answer.ActionType == ConditionActionType.SkipToQuestion)
{
<option value="1" selected>🎯 Skip to specific question</option>
}
else
{
<option value="1">🎯 Skip to specific question</option>
}
@if (answer.ActionType == ConditionActionType.SkipCount)
{
<option value="2" selected>⏭️ Skip number of questions</option>
}
else
{
<option value="2">⏭️ Skip number of questions</option>
}
@if (answer.ActionType == ConditionActionType.EndSurvey)
{
<option value="3" selected>🏁 End survey</option>
}
else
{
<option value="3">🏁 End survey</option>
}
</select>
</div>
<!-- Skip to Question Option -->
@{
var skipToDisplay = answer.ActionType == ConditionActionType.SkipToQuestion ? "block" : "none";
}
<div class="condition-option skip-to-question"
data-answer-id="@answer.AnswerId"
style="display: @skipToDisplay;">
<div class="form-group">
<label class="form-label">🎯 Go to question:</label>
<select class="form-control target-question-select">
@for (int targetQ = questionIndex + 2; targetQ <= ViewBag.TotalQuestions; targetQ++)
{
var targetQuestionText = "";
if (targetQ <= Model.Questions.Count)
{
var targetQuestionObj = Model.Questions[targetQ - 1];
targetQuestionText = targetQuestionObj.QuestionText.Length > 50
? targetQuestionObj.QuestionText.Substring(0, 50) + "..."
: targetQuestionObj.QuestionText;
}
@if (answer.TargetQuestionNumber == targetQ)
{
<option value="@targetQ" selected>
Q@(targetQ): @targetQuestionText
</option>
}
else
{
<option value="@targetQ">
Q@(targetQ): @targetQuestionText
</option>
}
}
</select>
</div>
</div>
<!-- Skip Count Option -->
@{
var skipCountDisplay = answer.ActionType == ConditionActionType.SkipCount ? "block" : "none";
var maxSkip = Math.Max(1, ViewBag.TotalQuestions - questionIndex - 1);
var skipValue = answer.SkipCount ?? 1;
}
<div class="condition-option skip-count"
data-answer-id="@answer.AnswerId"
style="display: @skipCountDisplay;">
<div class="form-group">
<label class="form-label">⏭️ Number of questions to skip:</label>
@if (maxSkip > 0)
{
<input type="number" class="form-control skip-count-input"
min="1" max="@maxSkip"
value="@skipValue" />
<div class="small-text">Maximum: @maxSkip questions</div>
}
else
{
<input type="number" class="form-control skip-count-input"
value="1" readonly />
<div class="small-text">No more questions to skip</div>
}
</div>
</div>
<!-- End Survey Option -->
@{
var endSurveyDisplay = answer.ActionType == ConditionActionType.EndSurvey ? "block" : "none";
}
<div class="condition-option end-survey"
data-answer-id="@answer.AnswerId"
style="display: @endSurveyDisplay;">
<div class="form-group">
<label class="form-label">🏁 End message (optional):</label>
<input type="text" class="form-control end-message-input"
placeholder="Thank you for participating!"
value="@(answer.EndMessage ?? "")" />
</div>
</div>
<!-- Condition Summary -->
<div class="condition-summary">
<strong>
<i class="fas fa-info-circle"></i>
Result:
</strong>
<span class="summary-text">@GetConditionSummary(answer)</span>
</div>
</div>
<!-- Individual Answer Actions -->
<div class="answer-actions">
<span class="answer-status" data-answer-id="@answer.AnswerId"></span>
<button type="button" class="btn-reset-answer" onclick="resetAnswer(@answer.AnswerId)">
<i class="fas fa-undo"></i> Reset
</button>
<button type="button" class="btn-save-answer" onclick="saveAnswer(@answer.AnswerId)">
<i class="fas fa-save"></i> Save
</button>
</div>
</div>
}
</div>
}
else
{
<div style="background: #e3f2fd; color: #1565c0; padding: 20px; border-radius: 8px; text-align: center;">
<i class="fas fa-info-circle"></i>
This question has no answers to configure conditions for.
</div>
}
</div>
</div>
}
</div>
<!-- Save Section -->
<div class="save-section">
<button type="submit" class="btn-primary">
<i class="fas fa-save"></i> Save Conditional Logic
</button>
<a href="@Url.Action("Index")" class="btn-secondary">
<i class="fas fa-arrow-left"></i> Back to Questionnaires
</a>
<button type="button" class="btn-secondary" onclick="previewLogic()">
<i class="fas fa-eye"></i> Preview Logic Flow
</button>
<button type="button" class="btn-secondary" onclick="clearAllConditions()">
<i class="fas fa-eraser"></i> Clear All Conditions
</button>
</div>
</form>
</div>
</div>
</div>
@section Scripts {
<script>
function showQuestion(questionIndex) {
const content = document.getElementById(`question-content-${questionIndex}`);
const showBtn = document.querySelector(`[onclick="showQuestion(${questionIndex})"]`);
const hideBtn = document.querySelector(`[onclick="hideQuestion(${questionIndex})"]`);
content.classList.add('show');
showBtn.classList.add('d-none');
hideBtn.classList.remove('d-none');
}
function hideQuestion(questionIndex) {
const content = document.getElementById(`question-content-${questionIndex}`);
const showBtn = document.querySelector(`[onclick="showQuestion(${questionIndex})"]`);
const hideBtn = document.querySelector(`[onclick="hideQuestion(${questionIndex})"]`);
content.classList.remove('show');
showBtn.classList.remove('d-none');
hideBtn.classList.add('d-none');
}
function toggleConditionOptions(selectElement) {
const answerId = selectElement.getAttribute('data-answer-id');
const selectedValue = selectElement.value;
// Hide all condition options for this answer
const conditionOptions = document.querySelectorAll(`[data-answer-id="${answerId}"].condition-option`);
conditionOptions.forEach(option => {
option.style.display = 'none';
});
// Show the relevant condition option
let targetClass = '';
switch(selectedValue) {
case '1': targetClass = 'skip-to-question'; break;
case '2': targetClass = 'skip-count'; break;
case '3': targetClass = 'end-survey'; break;
}
if (targetClass) {
const targetOption = document.querySelector(`[data-answer-id="${answerId}"].${targetClass}`);
if (targetOption) {
targetOption.style.display = 'block';
}
}
updateConditionSummary(selectElement);
updateQuestionHeaderStatus();
markAnswerAsModified(answerId);
}
function updateConditionSummary(selectElement) {
const answerId = selectElement.getAttribute('data-answer-id');
const selectedValue = selectElement.value;
const summaryElement = selectElement.closest('.answer-content').querySelector('.summary-text');
let summaryText = '';
switch(selectedValue) {
case '0':
summaryText = 'Continue to the next question normally';
break;
case '1':
const targetSelect = selectElement.closest('.answer-content').querySelector('.target-question-select');
if (targetSelect) {
const selectedOption = targetSelect.options[targetSelect.selectedIndex];
const optionText = selectedOption ? selectedOption.text : '';
summaryText = `Jump to ${optionText}`;
} else {
summaryText = 'Jump to specific question';
}
break;
case '2':
const skipInput = selectElement.closest('.answer-content').querySelector('.skip-count-input');
const skipCount = skipInput ? skipInput.value : '1';
summaryText = `Skip ${skipCount} question(s)`;
break;
case '3':
summaryText = 'End the survey immediately';
break;
}
if (summaryElement) {
summaryElement.textContent = summaryText;
}
}
function updateQuestionHeaderStatus() {
document.querySelectorAll('.question-card').forEach(card => {
const actionSelects = card.querySelectorAll('.action-type-select');
let hasConditions = false;
actionSelects.forEach(select => {
if (select.value !== '0') {
hasConditions = true;
}
});
const header = card.querySelector('.question-header');
if (hasConditions) {
header.classList.add('has-logic');
} else {
header.classList.remove('has-logic');
}
// Update answer cards
actionSelects.forEach(select => {
const answerCard = select.closest('.answer-card');
if (select.value !== '0') {
answerCard.classList.add('has-condition');
} else {
answerCard.classList.remove('has-condition');
}
});
});
}
function markAnswerAsModified(answerId) {
const answerCard = document.querySelector(`[data-answer-id="${answerId}"]`).closest('.answer-card');
const statusElement = answerCard.querySelector('.answer-status');
if (statusElement && !statusElement.classList.contains('modified')) {
statusElement.textContent = '● Modified';
statusElement.className = 'answer-status modified';
}
}
function saveAnswer(answerId) {
const answerCard = document.querySelector(`[data-answer-id="${answerId}"]`).closest('.answer-card');
const actionSelect = answerCard.querySelector('.action-type-select');
const statusElement = answerCard.querySelector('.answer-status');
if (!actionSelect) return;
const actionType = actionSelect.value;
let condition = {
ActionType: parseInt(actionType)
};
// Get additional data based on action type
switch(actionType) {
case '1': // Skip to question
const targetSelect = answerCard.querySelector('.target-question-select');
if (targetSelect && targetSelect.value && targetSelect.closest('.condition-option').style.display !== 'none') {
condition.TargetQuestionNumber = parseInt(targetSelect.value);
}
break;
case '2': // Skip count
const skipInput = answerCard.querySelector('.skip-count-input');
if (skipInput && skipInput.value && skipInput.closest('.condition-option').style.display !== 'none') {
condition.SkipCount = parseInt(skipInput.value);
}
break;
case '3': // End survey
const endMessageInput = answerCard.querySelector('.end-message-input');
if (endMessageInput && endMessageInput.closest('.condition-option').style.display !== 'none') {
condition.EndMessage = endMessageInput.value || null;
}
break;
}
// Here you could make an AJAX call to save individual answer
// For now, we'll just show success message and update status
statusElement.textContent = '✓ Saved';
statusElement.className = 'answer-status saved';
updateQuestionHeaderStatus();
showToast('Answer condition saved successfully!', 'success');
// Optional: Make AJAX call to server
// fetch('/YourController/SaveAnswerCondition', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({
// AnswerId: answerId,
// ConditionJson: JSON.stringify(condition)
// })
// });
}
function resetAnswer(answerId) {
if (confirm('Are you sure you want to reset this answer to "Continue to next question"?')) {
const answerCard = document.querySelector(`[data-answer-id="${answerId}"]`).closest('.answer-card');
const actionSelect = answerCard.querySelector('.action-type-select');
const statusElement = answerCard.querySelector('.answer-status');
if (actionSelect) {
actionSelect.value = '0';
toggleConditionOptions(actionSelect);
statusElement.textContent = '↻ Reset';
statusElement.className = 'answer-status modified';
updateQuestionHeaderStatus();
showToast('Answer condition reset successfully!', 'success');
}
}
}
function showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${type === 'success' ? '#28a745' : '#dc3545'};
color: white;
padding: 12px 20px;
border-radius: 6px;
z-index: 10000;
opacity: 0;
transition: opacity 0.3s ease;
`;
document.body.appendChild(toast);
// Fade in
setTimeout(() => {
toast.style.opacity = '1';
}, 100);
// Remove after 3 seconds
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => {
if (document.body.contains(toast)) {
document.body.removeChild(toast);
}
}, 300);
}, 3000);
}
function clearAllConditions() {
if (confirm('🗑️ Are you sure you want to reset all conditions to "Continue to next question"?\n\nThis action cannot be undone and will remove all your conditional logic rules.')) {
document.querySelectorAll('.action-type-select').forEach(select => {
select.value = '0';
toggleConditionOptions(select);
});
// Reset all status indicators
document.querySelectorAll('.answer-status').forEach(status => {
status.textContent = '↻ Reset';
status.className = 'answer-status modified';
});
updateQuestionHeaderStatus();
alert('✅ All conditions have been cleared!\n\nDon\'t forget to save your changes.');
}
}
function previewLogic() {
const conditions = [];
const answerContents = document.querySelectorAll('.answer-content');
answerContents.forEach(container => {
const actionSelect = container.querySelector('.action-type-select');
if (actionSelect && actionSelect.value !== '0') {
const summaryText = container.querySelector('.summary-text').textContent;
const answerText = container.querySelector('.answer-text').textContent.trim();
conditions.push(`• ${answerText} → ${summaryText}`);
}
});
if (conditions.length === 0) {
alert('📋 No conditional logic configured yet.\n\nAll answers will continue to the next question normally.');
} else {
alert('🔮 Active Conditional Logic Rules:\n\n' + conditions.join('\n\n'));
}
}
// Form submission handling
document.getElementById('logicForm').addEventListener('submit', function(e) {
e.preventDefault();
const conditions = [];
const answerContents = document.querySelectorAll('.answer-content');
answerContents.forEach((container, index) => {
const actionSelect = container.querySelector('.action-type-select');
if (!actionSelect) return;
const answerId = actionSelect.getAttribute('data-answer-id');
const actionType = actionSelect.value;
let condition = {
ActionType: parseInt(actionType)
};
// Get additional data based on action type
switch(actionType) {
case '1': // Skip to question
const targetSelect = container.querySelector('.target-question-select');
if (targetSelect && targetSelect.value && targetSelect.closest('.condition-option').style.display !== 'none') {
condition.TargetQuestionNumber = parseInt(targetSelect.value);
}
break;
case '2': // Skip count
const skipInput = container.querySelector('.skip-count-input');
if (skipInput && skipInput.value && skipInput.closest('.condition-option').style.display !== 'none') {
condition.SkipCount = parseInt(skipInput.value);
}
break;
case '3': // End survey
const endMessageInput = container.querySelector('.end-message-input');
if (endMessageInput && endMessageInput.closest('.condition-option').style.display !== 'none') {
condition.EndMessage = endMessageInput.value || null;
}
break;
}
const conditionJson = JSON.stringify(condition);
conditions.push({
AnswerId: parseInt(answerId),
ConditionJson: conditionJson
});
});
// Remove any existing hidden inputs to avoid duplicates
const existingInputs = this.querySelectorAll('input[name^="Conditions["]');
existingInputs.forEach(input => input.remove());
// Create hidden inputs for conditions
const form = this;
conditions.forEach((condition, index) => {
const answerIdInput = document.createElement('input');
answerIdInput.type = 'hidden';
answerIdInput.name = `Conditions[${index}].AnswerId`;
answerIdInput.value = condition.AnswerId;
form.appendChild(answerIdInput);
const conditionInput = document.createElement('input');
conditionInput.type = 'hidden';
conditionInput.name = `Conditions[${index}].ConditionJson`;
conditionInput.value = condition.ConditionJson;
form.appendChild(conditionInput);
});
// Submit the form
form.submit();
});
// Initialize condition summaries on page load
document.addEventListener('DOMContentLoaded', function() {
const actionSelects = document.querySelectorAll('.action-type-select');
actionSelects.forEach(updateConditionSummary);
// Add multi-answers class for questions with multiple answers
document.querySelectorAll('.answers-grid').forEach(grid => {
const answerCards = grid.querySelectorAll('.answer-card');
if (answerCards.length > 1) {
grid.classList.add('multi-answers');
}
});
// Add event listeners to target question selects
const targetSelects = document.querySelectorAll('.target-question-select');
targetSelects.forEach(select => {
select.addEventListener('change', function() {
const actionSelect = this.closest('.answer-content').querySelector('.action-type-select');
if (actionSelect) {
updateConditionSummary(actionSelect);
const answerId = actionSelect.getAttribute('data-answer-id');
markAnswerAsModified(answerId);
}
});
});
// Add event listeners to skip count inputs
const skipInputs = document.querySelectorAll('.skip-count-input');
skipInputs.forEach(input => {
input.addEventListener('input', function() {
const actionSelect = this.closest('.answer-content').querySelector('.action-type-select');
if (actionSelect) {
updateConditionSummary(actionSelect);
const answerId = actionSelect.getAttribute('data-answer-id');
markAnswerAsModified(answerId);
}
});
});
// Add event listeners to end message inputs
const endMessageInputs = document.querySelectorAll('.end-message-input');
endMessageInputs.forEach(input => {
input.addEventListener('input', function() {
const actionSelect = this.closest('.answer-content').querySelector('.action-type-select');
if (actionSelect) {
const answerId = actionSelect.getAttribute('data-answer-id');
markAnswerAsModified(answerId);
}
});
});
// Initialize answer status indicators
document.querySelectorAll('.answer-status').forEach(status => {
status.textContent = '';
});
// Update header status on page load
updateQuestionHeaderStatus();
});
</script>
}
@functions {
string GetConditionSummary(AnswerConditionViewModel answer)
{
switch (answer.ActionType)
{
case ConditionActionType.Continue:
return "Continue to the next question normally";
case ConditionActionType.SkipToQuestion:
if (answer.TargetQuestionNumber.HasValue && answer.TargetQuestionNumber.Value <= Model.Questions.Count)
{
var targetQuestion = Model.Questions[answer.TargetQuestionNumber.Value - 1];
var questionPreview = targetQuestion.QuestionText.Length > 30
? targetQuestion.QuestionText.Substring(0, 30) + "..."
: targetQuestion.QuestionText;
return $"Jump to Q{answer.TargetQuestionNumber}: {questionPreview}";
}
return $"Jump to Question {answer.TargetQuestionNumber}";
case ConditionActionType.SkipCount:
return $"Skip {answer.SkipCount} question(s)";
case ConditionActionType.EndSurvey:
return "End the survey immediately";
default:
return "No condition set";
}
}
}