HH/templates/surveys/public_form.html
2026-04-19 10:53:12 +03:00

811 lines
39 KiB
HTML

{% load i18n %}
{% load survey_filters %}
{% load static %}
<!DOCTYPE html>
<html lang="{{ language }}" dir="{% if language == 'ar' %}rtl{% else %}ltr{% endif %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>{% if language == 'ar' %}استبيان رضا المرضى{% else %}Patient Satisfaction Survey{% endif %} - PX360</title>
<link rel="stylesheet" href="{% static 'dist/css/tailwind.css' %}">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
body { font-family: 'Inter', sans-serif; }
.input-line { border-bottom: 2px solid #cbd5e1; outline: none; transition: border-color 0.3s; }
.input-line:focus { border-bottom-color: #005696; }
.rating-star { transition: all 0.2s; cursor: pointer; }
.rating-star svg { transition: all 0.15s ease; }
.rating-star:hover svg { transform: scale(1.2); }
.nps-button { transition: all 0.15s; }
.nps-button.selected {
background: #005696;
color: white;
border-color: #005696;
transform: scale(1.08);
}
.likert-option.selected { color: #005696; font-weight: 600; }
.step-slide {
position: relative;
width: 100%;
}
.step-slide.active {
}
.step-slide.exit-left {
display: none;
}
.step-slide.exit-right {
display: none;
}
.step-slide.enter-right {
display: none;
}
.step-slide.enter-left {
display: none;
}
.step-container {
position: relative;
}
.nav-btn {
transition: all 0.2s ease;
}
.nav-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.nav-btn:not(:disabled):hover {
transform: scale(1.05);
}
.ok-btn {
background: #005696;
transition: all 0.2s ease;
}
.ok-btn:hover { background: #007bbd; }
.ok-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.ok-btn:not(:disabled):hover { transform: scale(1.05); }
.fade-in {
animation: fadeIn 0.4s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body class="bg-gray-100 p-4 md:p-10 min-h-screen flex flex-col">
<!-- Language Toggle -->
<div class="fixed top-4 {% if language == 'ar' %}left-4{% else %}right-4{% endif %} z-50">
<a href="?lang={% if language == 'ar' %}en{% else %}ar{% endif %}"
class="flex items-center gap-1.5 bg-white text-navy px-4 py-2 rounded-full shadow-md hover:shadow-lg transition-shadow text-sm font-semibold border border-gray-200">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
</svg>
{% if language == 'ar' %}English{% else %}العربية{% endif %}
</a>
</div>
<div class="max-w-4xl mx-auto w-full bg-white shadow-2xl relative border-t-[20px] border-navy flex-1 flex flex-col">
<!-- Survey Header -->
<div class="p-6 md:p-8 pb-2 flex justify-between items-start">
<div class="flex items-center">
<img src="{% static 'img/HH_P_H_Logo.png' %}" alt="HH Hospital" class="h-10 md:h-12 object-contain">
</div>
<div class="text-end">
<h1 class="text-xl md:text-2xl font-bold text-[#2c3e50]">{% if language == 'ar' %}استبيان رضا{% else %}Patient{% endif %}</h1>
<h1 class="text-xl md:text-2xl font-bold text-[#2c3e50]">{% if language == 'ar' %}المرضى{% else %}Satisfaction Survey{% endif %}</h1>
</div>
</div>
<!-- Progress Bar -->
<div class="px-6 md:px-10 pt-2 pb-2">
<div class="h-1.5 bg-gray-200 rounded-full overflow-hidden">
<div id="progressBar" class="h-full bg-navy rounded-full transition-all duration-500" style="width: 0%"></div>
</div>
<p id="progressText" class="text-xs text-gray-400 mt-1.5 text-center">
{% if language == 'ar' %}السؤال 1 من {{ total_questions }}{% else %}Question 1 of {{ total_questions }}{% endif %}
</p>
</div>
<!-- Step-by-step Question Container -->
<div class="flex-1 flex flex-col">
<div id="step-container" class="step-container flex-1 px-6 md:px-10 py-6">
<!-- Questions are rendered here by JavaScript -->
</div>
<!-- Navigation Buttons -->
<div id="nav-buttons" class="px-6 md:px-10 pb-6 flex items-center justify-between gap-4">
<button type="button" id="prevBtn" class="nav-btn flex items-center gap-2 px-5 py-3 text-gray-500 hover:text-navy rounded-xl font-semibold" disabled>
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="{% if language == 'ar' %}9 18 15 12 9 6{% else %}15 18 9 12 15 6{% endif %}"/>
</svg>
{% if language == 'ar' %}السابق{% else %}Back{% endif %}
</button>
<button type="button" id="nextBtn" class="ok-btn flex items-center gap-2 px-8 py-3 text-white rounded-xl font-bold text-base">
{% if language == 'ar' %}التالي{% else %}Next{% endif %}
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="{% if language == 'ar' %}15 18 9 12 15 6{% else %}9 18 15 12 9 6{% endif %}"/>
</svg>
</button>
</div>
</div>
<!-- Comment & Submit (shown on final step) -->
<div id="final-section" class="hidden px-6 md:px-10 pb-8">
<div class="border-t border-gray-200 pt-6">
<p class="font-bold text-base flex items-center gap-2 mb-1">
{% if language == 'ar' %}
ملاحظات إضافية (اختياري)
{% else %}
Additional Comments (Optional)
{% endif %}
</p>
<p class="text-sm text-gray-500 mb-3">
{% if language == 'ar' %}
شارك أي ملاحظات أو اقتراحات إضافية حول تجربتك
{% else %}
Share any additional comments or suggestions about your experience
{% endif %}
</p>
<textarea id="survey_comment"
class="w-full border-b-2 border-gray-300 outline-none bg-transparent text-[#2c3e50] focus:border-navy transition-colors resize-none" rows="3"
placeholder="{% if language == 'ar' %}اكتب ملاحظاتك هنا...{% else %}Write your comments here...{% endif %}"></textarea>
<button type="button" id="submitBtn" class="w-full mt-6 bg-navy hover:bg-navy-dark text-white px-8 py-4 rounded-xl font-bold text-lg transition-all flex items-center justify-center gap-2">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>
</svg>
{% if language == 'ar' %}إرسال الاستبيان{% else %}Submit Survey{% endif %}
</button>
</div>
</div>
<!-- Footer -->
<div class="mt-auto bg-navy text-white p-6 text-center space-y-1">
<p class="text-sm font-medium">
{% if language == 'ar' %}
شكراً لمشاركتك في تحسين تجربة المرضى
{% else %}
Thank you for taking the time to complete our patient satisfaction survey.
{% endif %}
</p>
<p class="text-sm font-medium opacity-75">
{% if language == 'ar' %}
ملاحظاتك ستساعدنا في تقديم رعاية أفضل
{% else %}
Your feedback will help us improve patient experience.
{% endif %}
</p>
<p class="text-xs font-medium opacity-50 mt-2">
Powered by <a href="https://tenhal.sa" target="_blank" class="text-white hover:underline">tenhal.sa</a>
</p>
</div>
<!-- Decorative Corner Triangles -->
<div class="absolute bottom-0 left-0 w-full h-12 pointer-events-none z-10">
<div class="absolute bottom-0 left-0 border-r-[100px] border-b-[50px] border-r-transparent border-b-navy-dark"></div>
<div class="absolute bottom-0 right-0 border-l-[100px] border-b-[50px] border-l-transparent border-b-navy-dark"></div>
</div>
</div>
{% csrf_token %}
<!-- Fallback form for no-JS -->
<noscript>
<form method="post" id="fallbackForm" class="max-w-3xl mx-auto mt-4 bg-white p-8 rounded-xl shadow">
<input type="hidden" name="language" value="{{ language }}">
{% for question in questions %}
<div class="mb-6">
<p class="font-bold mb-2">{{ forloop.counter }}. {{ question.text }}</p>
<input type="text" name="question_{{ question.id }}" {% if question.is_required %}required{% endif %}>
</div>
{% endfor %}
<textarea name="comment" placeholder="Comments (optional)" class="w-full border p-2 mb-4" rows="3"></textarea>
<button type="submit" class="bg-navy text-white px-8 py-3 rounded-xl font-bold">Submit</button>
</form>
</noscript>
<script>
(function() {
var surveyToken = "{{ survey.access_token }}";
var language = "{{ language }}";
var questionsData = {{ questions_json|safe }};
var routingRules = {{ routing_rules_json|safe }};
var isRtl = language === 'ar';
var csrfToken = document.querySelector('[name=csrfmiddlewaretoken]') ? document.querySelector('[name=csrfmiddlewaretoken]').value : '';
if (!csrfToken) {
var metaTag = document.createElement('meta');
metaTag.name = 'csrfmiddlewaretoken';
document.head.appendChild(metaTag);
}
var getCsrf = function() {
var el = document.querySelector('input[name=csrfmiddlewaretoken]');
return el ? el.value : '';
};
var visibleQuestions = questionsData.filter(function(q) { return !q.is_conditional; });
var currentIndex = 0;
var answers = {};
var answerHistory = [];
var stepStartTime = Date.now();
var surveyStarted = false;
var isTransitioning = false;
var screenState = 'instructions';
var hospitalName = "{{ survey.survey_template.hospital.name|default:'Hospital'|escapejs }}";
var requiresConsent = {{ survey.survey_template.requires_consent|yesno:"true,false" }};
var instructionsText = isRtl
? "{{ survey.survey_template.instructions_ar|default:''|escapejs }}" || ''
: "{{ survey.survey_template.instructions_en|default:''|escapejs }}" || '';
var consentText = isRtl
? "{{ survey.survey_template.consent_text_ar|default:''|escapejs }}" || ''
: "{{ survey.survey_template.consent_text_en|default:''|escapejs }}" || '';
var todayDate = new Date().toISOString().split('T')[0];
instructionsText = instructionsText.replace(/Facility Name/g, hospitalName).replace(/\[Date\]/g, todayDate);
var container = document.getElementById('step-container');
var prevBtn = document.getElementById('prevBtn');
var nextBtn = document.getElementById('nextBtn');
var submitBtn = document.getElementById('submitBtn');
var finalSection = document.getElementById('final-section');
var navButtons = document.getElementById('nav-buttons');
var progressBar = document.getElementById('progressBar');
var progressText = document.getElementById('progressText');
function t(en, ar) {
return isRtl ? ar : en;
}
function trackSurveyStart() {
if (surveyStarted) return;
fetch('/surveys/s/' + surveyToken + '/track-start/', {
method: 'POST',
headers: { 'X-CSRFToken': getCsrf(), 'Content-Type': 'application/json' },
credentials: 'same-origin'
}).catch(function() {});
surveyStarted = true;
}
function updateProgress() {
var total = visibleQuestions.length;
var current = currentIndex + 1;
var pct = Math.min((current / total) * 100, 100);
progressBar.style.width = pct + '%';
progressText.textContent = isRtl
? 'السؤال ' + current + ' من ' + total
: 'Question ' + current + ' of ' + total;
}
function updateContainerHeight() {
container.style.height = 'auto';
}
function renderQuestion(q, index) {
var isLast = index === visibleQuestions.length - 1;
var num = index + 1;
var html = '<div class="step-slide active" data-step="' + index + '">';
html += '<div class="fade-in">';
html += '<div class="flex items-start gap-3 mb-6">';
html += '<span class="font-bold text-navy text-2xl flex-shrink-0">' + num + '.</span>';
html += '<div>';
html += '<p class="font-bold text-lg leading-relaxed">';
html += (isRtl && q.text_ar) ? escapeHtml(q.text_ar) : escapeHtml(q.text);
if (q.is_required) html += '<span class="text-red-500 ms-1">*</span>';
html += '</p></div></div>';
html += '<div class="ms-7 md:ms-10">';
if (q.question_type === 'rating') {
html += '<div class="flex gap-4 justify-center flex-wrap">';
for (var i = 1; i <= 5; i++) {
html += '<button type="button" class="rating-star text-gray-300 focus:outline-none" data-value="' + i + '" onclick="window._selectRating(this)">';
html += '<svg xmlns="http://www.w3.org/2000/svg" width="42" height="42" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>';
html += '</button>';
}
html += '</div>';
html += '<input type="hidden" id="answer_' + q.id + '" data-question-id="' + q.id + '">';
}
else if (q.question_type === 'nps') {
html += '<div class="nps-scale flex gap-2 justify-center flex-wrap">';
for (var i = 0; i <= 10; i++) {
html += '<button type="button" class="nps-button w-10 h-10 md:w-12 md:h-12 border border-gray-300 rounded-lg bg-white font-semibold text-sm focus:outline-none hover:border-navy hover:text-navy" data-value="' + i + '" onclick="window._selectNPS(this)">';
html += i;
html += '</button>';
}
html += '</div>';
html += '<div class="flex justify-between text-xs text-gray-400 mt-2">';
html += '<span>' + t('Not likely', 'غير محتمل') + '</span>';
html += '<span>' + t('Very likely', 'محتمل جداً') + '</span>';
html += '</div>';
html += '<input type="hidden" id="answer_' + q.id + '" data-question-id="' + q.id + '">';
}
else if (q.question_type === 'likert') {
var likertLabels = isRtl
? ['1:أرفض بشدة', '2:أرفض', '3:محايد', '4:أوافق', '5:أوافق بشدة']
: ['1:Strongly Disagree', '2:Disagree', '3:Neutral', '4:Agree', '5:Strongly Agree'];
likertLabels.forEach(function(item) {
var parts = item.split(':');
html += '<label class="likert-option flex items-center cursor-pointer gap-3 py-2 hover:text-navy transition-colors text-base">';
html += '<input type="radio" name="answer_' + q.id + '" value="' + parts[0] + '" class="w-5 h-5 accent-navy" onchange="window._selectOption(this)">';
html += '<span>' + parts[1] + '</span>';
html += '</label>';
});
html += '<input type="hidden" id="answer_' + q.id + '" data-question-id="' + q.id + '">';
}
else if (q.question_type === 'yes_no') {
html += '<div class="flex gap-8">';
html += '<label class="flex items-center cursor-pointer gap-3 py-2">';
html += '<input type="radio" name="answer_' + q.id + '" value="yes" class="w-5 h-5 accent-navy" onchange="window._selectOption(this)">';
html += '<span class="text-lg">' + t('Yes', 'نعم') + '</span>';
html += '</label>';
html += '<label class="flex items-center cursor-pointer gap-3 py-2">';
html += '<input type="radio" name="answer_' + q.id + '" value="no" class="w-5 h-5 accent-navy" onchange="window._selectOption(this)">';
html += '<span class="text-lg">' + t('No', 'لا') + '</span>';
html += '</label>';
html += '</div>';
html += '<input type="hidden" id="answer_' + q.id + '" data-question-id="' + q.id + '">';
}
else if (q.question_type === 'multiple_choice') {
var choices = q.choices_json || [];
choices.forEach(function(choice) {
var label = (isRtl && choice.label_ar) ? choice.label_ar : choice.label;
html += '<label class="likert-option flex items-center cursor-pointer gap-3 py-2 hover:text-navy transition-colors text-base">';
html += '<input type="radio" name="answer_' + q.id + '" value="' + escapeAttr(choice.value) + '" class="w-5 h-5 accent-navy" onchange="window._selectOption(this)">';
html += '<span>' + escapeHtml(label) + '</span>';
html += '</label>';
});
html += '<input type="hidden" id="answer_' + q.id + '" data-question-id="' + q.id + '">';
}
else if (q.question_type === 'text') {
html += '<input type="text" id="answer_' + q.id + '" data-question-id="' + q.id + '" class="input-line w-full pb-2 text-lg bg-transparent text-[#2c3e50]" placeholder="' + t('Type your answer here...', 'أدخل إجابتك هنا') + '" oninput="window._onTextInput(this)">';
}
else if (q.question_type === 'textarea') {
html += '<textarea id="answer_' + q.id + '" data-question-id="' + q.id + '" class="input-line w-full pb-2 text-lg bg-transparent text-[#2c3e50] resize-none" rows="3" placeholder="' + t('Type your answer here...', 'أدخل إجابتك هنا') + '" oninput="window._onTextInput(this)"></textarea>';
}
html += '</div>';
html += '<div class="validation-error hidden ms-7 md:ms-10 mt-3 text-red-500 text-sm font-medium">';
html += t('This question is required', 'هذا السؤال مطلوب');
html += '</div>';
html += '</div></div>';
return html;
}
function escapeHtml(str) {
if (!str) return '';
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
function escapeAttr(str) {
if (!str) return '';
return String(str).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
function getCurrentAnswer() {
var q = visibleQuestions[currentIndex];
if (!q) return null;
var el = document.getElementById('answer_' + q.id);
if (!el) return null;
if (el.tagName === 'INPUT' && el.type === 'hidden') {
var radios = document.querySelectorAll('input[name="answer_' + q.id + '"]');
for (var i = 0; i < radios.length; i++) {
if (radios[i].checked) return radios[i].value;
}
return el.value || null;
}
return el.value || null;
}
function isCurrentAnswered() {
var answer = getCurrentAnswer();
return answer !== null && String(answer).trim() !== '';
}
function evaluateRules(questionId, answerValue) {
var matchingRules = routingRules.filter(function(r) {
return r.source_question === String(questionId);
});
matchingRules.sort(function(a, b) { return (a.order || 0) - (b.order || 0); });
for (var i = 0; i < matchingRules.length; i++) {
var rule = matchingRules[i];
if (evaluateCondition(rule.operator, rule.value, answerValue)) {
return rule;
}
}
return null;
}
function evaluateCondition(operator, ruleValue, answerValue) {
if (operator === 'answered') return answerValue !== null && String(answerValue).trim() !== '';
if (operator === 'not_answered') return answerValue === null || String(answerValue).trim() === '';
if (answerValue === null || String(answerValue).trim() === '') return false;
var ansStr = String(answerValue).trim();
if (operator === 'equals') return ansStr === String(ruleValue);
if (operator === 'not_equals') return ansStr !== String(ruleValue);
if (operator === 'contains') return String(ruleValue).indexOf(ansStr) >= 0 || ansStr.indexOf(String(ruleValue)) >= 0;
if (operator === 'gt' || operator === 'lt') {
var aNum = parseFloat(ansStr);
var rNum = parseFloat(ruleValue);
if (isNaN(aNum) || isNaN(rNum)) return false;
return operator === 'gt' ? aNum > rNum : aNum < rNum;
}
if (operator === 'in_list') {
var list = Array.isArray(ruleValue) ? ruleValue : [ruleValue];
for (var j = 0; j < list.length; j++) {
if (ansStr === String(list[j])) return true;
}
return false;
}
return false;
}
function showStep(newIndex, direction) {
if (isTransitioning) return;
if (newIndex < 0 || newIndex >= visibleQuestions.length) return;
isTransitioning = true;
var oldSlide = container.querySelector('.step-slide.active');
var newHtml = renderQuestion(visibleQuestions[newIndex], newIndex);
var temp = document.createElement('div');
temp.innerHTML = newHtml;
var newSlide = temp.firstElementChild;
if (direction === 'forward') {
newSlide.classList.remove('active');
newSlide.classList.add('enter-right');
} else {
newSlide.classList.remove('active');
newSlide.classList.add('enter-left');
}
container.appendChild(newSlide);
requestAnimationFrame(function() {
if (oldSlide) {
oldSlide.classList.remove('active');
oldSlide.classList.add(direction === 'forward' ? 'exit-left' : 'exit-right');
}
requestAnimationFrame(function() {
newSlide.classList.remove('enter-right', 'enter-left');
newSlide.classList.add('active');
updateContainerHeight();
});
});
setTimeout(function() {
if (oldSlide) oldSlide.remove();
isTransitioning = false;
updateContainerHeight();
}, 500);
currentIndex = newIndex;
updateNavButtons();
updateProgress();
stepStartTime = Date.now();
restoreAnswer(visibleQuestions[currentIndex].id);
}
function restoreAnswer(questionId) {
var answer = answers[questionId];
if (answer === undefined) return;
var hiddenEl = document.getElementById('answer_' + questionId);
if (!hiddenEl) return;
var q = visibleQuestions[currentIndex];
if (q.question_type === 'rating') {
hiddenEl.value = answer;
var stars = hiddenEl.parentElement.querySelectorAll('.rating-star');
for (var i = 0; i < stars.length; i++) {
var svg = stars[i].querySelector('svg');
if (i < parseInt(answer)) {
svg.setAttribute('fill', '#fbbf24');
svg.setAttribute('stroke', '#fbbf24');
stars[i].classList.add('active');
} else {
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', '#94a3b8');
stars[i].classList.remove('active');
}
}
} else if (q.question_type === 'nps') {
hiddenEl.value = answer;
var btns = hiddenEl.parentElement.querySelectorAll('.nps-button');
btns.forEach(function(b) {
b.classList.toggle('selected', b.getAttribute('data-value') === String(answer));
});
} else if (['likert', 'yes_no', 'multiple_choice'].indexOf(q.question_type) >= 0) {
var radios = document.querySelectorAll('input[name="answer_' + questionId + '"]');
radios.forEach(function(r) {
r.checked = r.value === String(answer);
if (r.checked && r.closest('.likert-option')) {
r.closest('.likert-option').classList.add('selected');
}
});
hiddenEl.value = answer;
} else {
hiddenEl.value = answer;
}
}
function updateNavButtons() {
prevBtn.disabled = currentIndex === 0;
var q = visibleQuestions[currentIndex];
var isLast = currentIndex === visibleQuestions.length - 1;
if (isLast && isCurrentAnswered()) {
finalSection.classList.remove('hidden');
navButtons.classList.add('hidden');
} else {
finalSection.classList.add('hidden');
navButtons.classList.remove('hidden');
}
nextBtn.innerHTML = t('Next <svg class="w-5 h-5 inline" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>', 'التالي <svg class="w-5 h-5 inline" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>');
}
function saveCurrentAnswer() {
var answer = getCurrentAnswer();
if (answer !== null) {
var q = visibleQuestions[currentIndex];
answers[q.id] = answer;
}
}
function goNext() {
var q = visibleQuestions[currentIndex];
if (q.is_required && !isCurrentAnswered()) {
var errEl = container.querySelector('.validation-error');
if (errEl) errEl.classList.remove('hidden');
return;
}
var errEl = container.querySelector('.validation-error');
if (errEl) errEl.classList.add('hidden');
saveCurrentAnswer();
trackSurveyStart();
var answer = getCurrentAnswer();
var matchedRule = evaluateRules(q.id, answer);
if (matchedRule) {
if (matchedRule.action === 'end_survey') {
submitSurvey(true);
return;
}
if (matchedRule.action === 'skip_to' && matchedRule.target_question) {
var targetIdx = visibleQuestions.findIndex(function(vq) { return vq.id === matchedRule.target_question; });
if (targetIdx >= 0 && targetIdx > currentIndex) {
showStep(targetIdx, 'forward');
return;
}
}
}
if (currentIndex < visibleQuestions.length - 1) {
showStep(currentIndex + 1, 'forward');
} else {
updateNavButtons();
}
}
function goPrev() {
if (currentIndex > 0) {
saveCurrentAnswer();
showStep(currentIndex - 1, 'back');
}
}
function submitSurvey(routedEnd) {
saveCurrentAnswer();
var formData = new FormData();
formData.append('language', language);
for (var qId in answers) {
if (answers.hasOwnProperty(qId)) {
formData.append('question_' + qId, answers[qId]);
}
}
var comment = document.getElementById('survey_comment');
if (comment) formData.append('comment', comment.value);
fetch('/surveys/s/' + surveyToken + '/', {
method: 'POST',
body: formData,
headers: { 'X-CSRFToken': getCsrf() },
credentials: 'same-origin'
}).then(function(r) {
if (r.redirected) {
window.location.href = r.url;
} else {
window.location.href = '/surveys/s/' + surveyToken + '/thank-you/?lang=' + language;
}
}).catch(function() {
var fallbackForm = document.createElement('form');
fallbackForm.method = 'POST';
fallbackForm.action = '/surveys/s/' + surveyToken + '/';
fallbackForm.innerHTML = '<input type="hidden" name="csrfmiddlewaretoken" value="' + getCsrf() + '"><input type="hidden" name="language" value="' + language + '">';
for (var qId in answers) {
if (answers.hasOwnProperty(qId)) {
fallbackForm.innerHTML += '<input type="hidden" name="question_' + qId + '" value="' + escapeAttr(answers[qId]) + '">';
}
}
var commentEl = document.getElementById('survey_comment');
if (commentEl && commentEl.value) {
fallbackForm.innerHTML += '<input type="hidden" name="comment" value="' + escapeAttr(commentEl.value) + '">';
}
document.body.appendChild(fallbackForm);
fallbackForm.submit();
});
}
window._selectRating = function(star) {
var value = parseInt(star.getAttribute('data-value'));
var container_ = star.parentElement;
var stars = container_.querySelectorAll('.rating-star');
stars.forEach(function(s, i) {
var svg = s.querySelector('svg');
if (i < value) {
svg.setAttribute('fill', '#fbbf24');
svg.setAttribute('stroke', '#fbbf24');
s.classList.add('active');
} else {
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', '#94a3b8');
s.classList.remove('active');
}
});
var hiddenInput = container_.parentElement.querySelector('input[type="hidden"]');
if (hiddenInput) hiddenInput.value = value;
updateNavButtons();
};
window._selectNPS = function(button) {
button.parentElement.querySelectorAll('.nps-button').forEach(function(b) { b.classList.remove('selected'); });
button.classList.add('selected');
var hiddenInput = button.parentElement.parentElement.querySelector('input[type="hidden"]');
if (hiddenInput) hiddenInput.value = button.getAttribute('data-value');
updateNavButtons();
};
window._selectOption = function(radio) {
var qContent = radio.closest('.step-slide');
if (qContent) qContent.querySelectorAll('.likert-option').forEach(function(opt) { opt.classList.remove('selected'); });
var label = radio.closest('.likert-option');
if (label) label.classList.add('selected');
var hiddenInput = radio.closest('.step-slide').querySelector('input[type="hidden"]');
if (hiddenInput) hiddenInput.value = radio.value;
updateNavButtons();
};
window._onTextInput = function(el) {
updateNavButtons();
};
nextBtn.addEventListener('click', goNext);
prevBtn.addEventListener('click', goPrev);
submitBtn.addEventListener('click', function() { submitSurvey(false); });
document.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
var activeEl = document.activeElement;
if (activeEl && activeEl.tagName === 'TEXTAREA') return;
e.preventDefault();
goNext();
}
});
if (visibleQuestions.length > 0) {
if (requiresConsent || instructionsText) {
showInstructions();
} else {
container.innerHTML = renderQuestion(visibleQuestions[0], 0);
updateNavButtons();
updateProgress();
updateContainerHeight();
}
}
function showInstructions() {
screenState = 'instructions';
prevBtn.style.display = 'none';
nextBtn.style.display = 'flex';
nextBtn.disabled = false;
nextBtn.innerHTML = isRtl
? 'التالي <svg class="w-5 h-5 inline" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>'
: 'OK <svg class="w-5 h-5 inline" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>';
finalSection.classList.add('hidden');
navButtons.classList.remove('hidden');
progressBar.style.width = '0%';
progressText.textContent = '';
var html = '<div class="step-slide active fade-in">';
html += '<div class="flex items-center gap-3 mb-6">';
html += '<svg class="w-8 h-8 text-navy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>';
html += '<h2 class="text-xl font-bold text-navy">' + (isRtl ? 'تعليمات الاستبيان' : 'Survey Instructions') + '</h2>';
html += '</div>';
html += '<div class="bg-blue-50 border border-blue-200 rounded-xl p-5 text-[#2c3e50] text-base leading-relaxed whitespace-pre-line">';
html += escapeHtml(instructionsText);
html += '</div>';
html += '</div>';
container.innerHTML = html;
setTimeout(function() { updateContainerHeight(); }, 50);
}
function showConsent() {
screenState = 'consent';
prevBtn.style.display = 'flex';
prevBtn.disabled = false;
nextBtn.style.display = 'flex';
nextBtn.disabled = true;
nextBtn.innerHTML = isRtl
? 'أوافق ومتابعة <svg class="w-5 h-5 inline" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>'
: 'I Agree <svg class="w-5 h-5 inline" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>';
var html = '<div class="step-slide active fade-in">';
html += '<div class="flex items-center gap-3 mb-6">';
html += '<svg class="w-8 h-8 text-amber-600" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>';
html += '<h2 class="text-xl font-bold text-navy">' + (isRtl ? 'الموافقة على المشاركة' : 'Patient Consent') + '</h2>';
html += '</div>';
html += '<div class="bg-amber-50 border border-amber-200 rounded-xl p-5 text-[#2c3e50] text-base leading-relaxed mb-5">';
html += escapeHtml(consentText);
html += '</div>';
html += '<label class="flex items-start gap-3 cursor-pointer group">';
html += '<input type="checkbox" id="consent-checkbox" class="mt-1 w-5 h-5 accent-navy rounded" onchange="window._toggleConsent(this)">';
html += '<span class="text-base font-semibold text-[#2c3e50] group-hover:text-navy transition-colors">';
html += isRtl ? 'أوافق على الشروط المذكورة أعلاه' : 'I agree to the terms above';
html += '</span>';
html += '</label>';
html += '</div>';
container.innerHTML = html;
setTimeout(function() { updateContainerHeight(); }, 50);
}
window._toggleConsent = function(cb) {
nextBtn.disabled = !cb.checked;
};
nextBtn.removeEventListener('click', goNext);
nextBtn.addEventListener('click', function() {
if (screenState === 'instructions') {
if (requiresConsent) {
showConsent();
} else {
screenState = 'questions';
prevBtn.style.display = 'flex';
nextBtn.style.display = 'flex';
container.innerHTML = renderQuestion(visibleQuestions[0], 0);
updateNavButtons();
updateProgress();
updateContainerHeight();
}
} else if (screenState === 'consent') {
var cb = document.getElementById('consent-checkbox');
if (!cb || !cb.checked) return;
screenState = 'questions';
prevBtn.style.display = 'flex';
container.innerHTML = renderQuestion(visibleQuestions[0], 0);
updateNavButtons();
updateProgress();
updateContainerHeight();
} else {
goNext();
}
});
prevBtn.removeEventListener('click', goPrev);
prevBtn.addEventListener('click', function() {
if (screenState === 'consent') {
showInstructions();
} else if (screenState === 'questions' && currentIndex > 0) {
goPrev();
}
});
})();
</script>
</body>
</html>