459 lines
23 KiB
HTML
459 lines
23 KiB
HTML
{% load i18n %}
|
|
{% load survey_filters %}
|
|
<!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>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
background: linear-gradient(135deg, #005696 0%, #007bbd 100%);
|
|
}
|
|
.rating-star {
|
|
transition: all 0.2s;
|
|
}
|
|
.rating-star.active {
|
|
color: #fbbf24;
|
|
transform: scale(1.1);
|
|
}
|
|
.rating-star:hover {
|
|
color: #fbbf24;
|
|
transform: scale(1.15);
|
|
}
|
|
.nps-button:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
.nps-button.selected {
|
|
background: #005696;
|
|
color: white;
|
|
border-color: #005696;
|
|
transform: scale(1.05);
|
|
}
|
|
.likert-option:hover {
|
|
border-color: #005696;
|
|
background: #eef6fb;
|
|
}
|
|
.likert-option.selected {
|
|
border-color: #005696;
|
|
background: #e0f0fa;
|
|
}
|
|
.yes-no-button:hover {
|
|
border-color: #005696;
|
|
transform: translateY(-2px);
|
|
}
|
|
.yes-no-button.selected {
|
|
background: #005696;
|
|
color: white;
|
|
border-color: #005696;
|
|
}
|
|
.question-card {
|
|
transition: all 0.2s;
|
|
}
|
|
.form-input:focus {
|
|
border-color: #005696;
|
|
box-shadow: 0 0 0 3px rgba(0, 86, 150, 0.1);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="min-h-screen">
|
|
<!-- Language Toggle -->
|
|
<div class="fixed top-4 {% if language == 'ar' %}left-4{% else %}right-4{% endif %} bg-white rounded-full px-4 py-2 shadow-lg z-50">
|
|
{% if language == 'ar' %}
|
|
<a href="?lang=en" class="text-navy hover:text-navy font-medium text-sm flex items-center gap-1">
|
|
<i data-lucide="languages" class="w-4 h-4"></i> English
|
|
</a>
|
|
{% else %}
|
|
<a href="?lang=ar" class="text-navy hover:text-navy font-medium text-sm flex items-center gap-1">
|
|
<i data-lucide="languages" class="w-4 h-4"></i> العربية
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="max-w-2xl mx-auto px-4 py-6 sm:py-8">
|
|
<div class="bg-white rounded-3xl shadow-xl overflow-hidden mb-6">
|
|
<!-- Survey Header -->
|
|
<div class="bg-gradient-to-br from-navy to-blue text-white p-8 text-center">
|
|
<div class="flex justify-center mb-4">
|
|
<div class="bg-white/20 p-3 rounded-2xl backdrop-blur-sm">
|
|
<i data-lucide="clipboard-list" class="w-10 h-10"></i>
|
|
</div>
|
|
</div>
|
|
<h1 class="text-2xl sm:text-3xl font-bold mb-2">
|
|
{% if language == 'ar' %}
|
|
{{ survey.survey_template.name_ar|default:survey.survey_template.name }}
|
|
{% else %}
|
|
{{ survey.survey_template.name }}
|
|
{% endif %}
|
|
</h1>
|
|
<p class="text-white/80 text-sm sm:text-base">
|
|
{% if language == 'ar' %}
|
|
شكراً لمشاركتك في تحسين تجربة المرضى
|
|
{% else %}
|
|
Thank you for helping us improve patient experience
|
|
{% endif %}
|
|
</p>
|
|
|
|
<!-- Progress Bar -->
|
|
<div class="bg-white/20 rounded-full h-2 mt-6 overflow-hidden">
|
|
<div class="bg-white h-full rounded-full transition-all duration-300" id="progressBar" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Survey Body -->
|
|
<div class="p-6 sm:p-8">
|
|
<!-- Error Messages -->
|
|
{% if errors %}
|
|
<div class="bg-red-50 border-l-4 border-red-500 text-red-700 p-4 rounded-xl mb-6">
|
|
<strong class="flex items-center gap-2 mb-2">
|
|
<i data-lucide="alert-circle" class="w-5 h-5"></i>
|
|
{% if language == 'ar' %}
|
|
يرجى تصحيح الأخطاء التالية:
|
|
{% else %}
|
|
Please correct the following errors:
|
|
{% endif %}
|
|
</strong>
|
|
<ul class="mb-0 mt-2 space-y-1">
|
|
{% for error in errors %}
|
|
<li>{{ error }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Survey Form -->
|
|
<form method="post" id="surveyForm">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="language" value="{{ language }}">
|
|
|
|
{% for question in questions %}
|
|
<div class="question-card mb-8 pb-8 border-b border-gray-100 last:border-b-0 last:pb-0 last:mb-0" data-question="{{ forloop.counter }}">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<span class="bg-navy text-white w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm flex-shrink-0">
|
|
{{ forloop.counter }}
|
|
</span>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-800">
|
|
{% if language == 'ar' and question.text_ar %}
|
|
{{ question.text_ar }}
|
|
{% else %}
|
|
{{ question.text }}
|
|
{% endif %}
|
|
{% if question.is_required %}
|
|
<span class="text-red-500 ml-1">*</span>
|
|
{% endif %}
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
|
|
{% if question.help_text or question.help_text_ar %}
|
|
<small class="text-gray-500 block mb-4 ml-11">
|
|
{% if language == 'ar' and question.help_text_ar %}
|
|
{{ question.help_text_ar }}
|
|
{% else %}
|
|
{{ question.help_text }}
|
|
{% endif %}
|
|
</small>
|
|
{% endif %}
|
|
|
|
<!-- Rating Question -->
|
|
{% if question.question_type == 'rating' %}
|
|
<div class="ml-11">
|
|
<div class="rating-stars flex gap-3 justify-center flex-wrap">
|
|
{% for i in "12345" %}
|
|
<button type="button" class="rating-star text-gray-300 text-4xl focus:outline-none"
|
|
data-value="{{ forloop.counter }}"
|
|
onclick="selectRating(this, 'question_{{ question.id }}')">
|
|
<i data-lucide="star" class="w-10 h-10 fill-current"></i>
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
<input type="hidden" name="question_{{ question.id }}" id="question_{{ question.id }}"
|
|
{% if question.is_required %}required{% endif %}>
|
|
</div>
|
|
|
|
<!-- NPS Question -->
|
|
{% elif question.question_type == 'nps' %}
|
|
<div class="ml-11">
|
|
<div class="nps-scale flex gap-2 justify-center flex-wrap">
|
|
{% for i in "012345678910" %}
|
|
<button type="button" class="nps-button w-10 h-10 sm:w-12 sm:h-12 border-2 border-gray-300 rounded-xl bg-white font-bold focus:outline-none transition-all"
|
|
data-value="{{ forloop.counter0 }}"
|
|
onclick="selectNPS(this, 'question_{{ question.id }}')">
|
|
{{ forloop.counter0 }}
|
|
</button>
|
|
{% endfor %}
|
|
</div>
|
|
<div class="flex justify-between text-xs text-gray-500 mt-2">
|
|
<span>
|
|
{% if language == 'ar' %}غير محتمل{% else %}Not likely{% endif %}
|
|
</span>
|
|
<span>
|
|
{% if language == 'ar' %}محتمل جداً{% else %}Very likely{% endif %}
|
|
</span>
|
|
</div>
|
|
<input type="hidden" name="question_{{ question.id }}" id="question_{{ question.id }}"
|
|
{% if question.is_required %}required{% endif %}>
|
|
</div>
|
|
|
|
<!-- Likert Scale -->
|
|
{% elif question.question_type == 'likert' %}
|
|
<div class="ml-11 space-y-2">
|
|
{% for value, label in "1:Strongly Disagree,2:Disagree,3:Neutral,4:Agree,5:Strongly Agree"|split:"," %}
|
|
{% with parts=label|split:":" %}
|
|
<label class="likert-option flex items-center gap-3 p-3 border-2 border-gray-200 rounded-xl cursor-pointer transition-all">
|
|
<input type="radio" name="question_{{ question.id }}" value="{{ parts.0 }}"
|
|
{% if question.is_required %}required{% endif %}
|
|
class="w-5 h-5 text-navy focus:ring-navy"
|
|
onchange="selectLikert(this)">
|
|
<span class="text-gray-700">{{ parts.1 }}</span>
|
|
</label>
|
|
{% endwith %}
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Yes/No Question -->
|
|
{% elif question.question_type == 'yes_no' %}
|
|
<div class="ml-11">
|
|
<div class="yes-no-buttons flex gap-4 justify-center">
|
|
<button type="button" class="yes-no-button flex-1 max-w-[140px] px-6 py-3 border-2 border-gray-200 rounded-xl bg-white font-semibold focus:outline-none transition-all"
|
|
data-value="yes"
|
|
onclick="selectYesNo(this, 'question_{{ question.id }}')">
|
|
{% if language == 'ar' %}نعم{% else %}Yes{% endif %}
|
|
</button>
|
|
<button type="button" class="yes-no-button flex-1 max-w-[140px] px-6 py-3 border-2 border-gray-200 rounded-xl bg-white font-semibold focus:outline-none transition-all"
|
|
data-value="no"
|
|
onclick="selectYesNo(this, 'question_{{ question.id }}')">
|
|
{% if language == 'ar' %}لا{% else %}No{% endif %}
|
|
</button>
|
|
</div>
|
|
<input type="hidden" name="question_{{ question.id }}" id="question_{{ question.id }}"
|
|
{% if question.is_required %}required{% endif %}>
|
|
</div>
|
|
|
|
<!-- Multiple Choice -->
|
|
{% elif question.question_type == 'multiple_choice' %}
|
|
<div class="ml-11 space-y-2">
|
|
{% for choice in question.choices_json %}
|
|
<label class="likert-option flex items-center gap-3 p-3 border-2 border-gray-200 rounded-xl cursor-pointer transition-all">
|
|
<input type="radio" name="question_{{ question.id }}" value="{{ choice.value }}"
|
|
{% if question.is_required %}required{% endif %}
|
|
class="w-5 h-5 text-navy focus:ring-navy"
|
|
onchange="selectLikert(this)">
|
|
<span class="text-gray-700">
|
|
{% if language == 'ar' and choice.label_ar %}
|
|
{{ choice.label_ar }}
|
|
{% else %}
|
|
{{ choice.label }}
|
|
{% endif %}
|
|
</span>
|
|
</label>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Text Input -->
|
|
{% elif question.question_type == 'text' %}
|
|
<div class="ml-11">
|
|
<input type="text" name="question_{{ question.id }}"
|
|
class="form-input w-full px-4 py-3 border-2 border-gray-200 rounded-xl text-gray-800 focus:outline-none transition-all"
|
|
{% if question.is_required %}required{% endif %}
|
|
placeholder="{% if language == 'ar' %}أدخل إجابتك هنا{% else %}Enter your answer here{% endif %}">
|
|
</div>
|
|
|
|
<!-- Text Area -->
|
|
{% elif question.question_type == 'textarea' %}
|
|
<div class="ml-11">
|
|
<textarea name="question_{{ question.id }}" class="form-input w-full px-4 py-3 border-2 border-gray-200 rounded-xl text-gray-800 focus:outline-none transition-all" rows="4"
|
|
{% if question.is_required %}required{% endif %}
|
|
placeholder="{% if language == 'ar' %}أدخل إجابتك هنا{% else %}Enter your answer here{% endif %}"></textarea>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<!-- Optional Comment Section -->
|
|
<div class="question-card mb-8 pb-8 border-b border-gray-100">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-4 flex items-center gap-2">
|
|
<i data-lucide="message-circle" class="w-5 h-5 text-gray-400"></i>
|
|
{% if language == 'ar' %}
|
|
ملاحظات إضافية (اختياري)
|
|
{% else %}
|
|
Additional Comments (Optional)
|
|
{% endif %}
|
|
</h3>
|
|
<small class="text-gray-500 block mb-3">
|
|
{% if language == 'ar' %}
|
|
شارك أي ملاحظات أو اقتراحات إضافية حول تجربتك
|
|
{% else %}
|
|
Share any additional comments or suggestions about your experience
|
|
{% endif %}
|
|
</small>
|
|
<textarea name="comment" id="survey_comment" class="form-input w-full px-4 py-3 border-2 border-gray-200 rounded-xl text-gray-800 focus:outline-none transition-all" rows="4"
|
|
placeholder="{% if language == 'ar' %}اكتب ملاحظاتك هنا...{% else %}Write your comments here...{% endif %}"></textarea>
|
|
</div>
|
|
|
|
<!-- Submit Button -->
|
|
<button type="submit" class="w-full bg-gradient-to-br from-navy to-blue text-white px-8 py-4 rounded-2xl font-bold text-lg hover:shadow-lg hover:shadow-blue-200 transition-all transform hover:-translate-y-1 flex items-center justify-center gap-2">
|
|
<i data-lucide="send" class="w-5 h-5"></i>
|
|
{% if language == 'ar' %}
|
|
إرسال الاستبيان
|
|
{% else %}
|
|
Submit Survey
|
|
{% endif %}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="text-center text-white">
|
|
<div class="flex items-center justify-center gap-2 mb-2">
|
|
<i data-lucide="heart" class="w-4 h-4 fill-current"></i>
|
|
<span class="text-sm font-medium">PX360</span>
|
|
</div>
|
|
<p class="text-white/80 text-xs">
|
|
{% if language == 'ar' %}
|
|
شكراً لمشاركتك في تحسين تجربة المرضى
|
|
{% else %}
|
|
Thank you for helping us improve patient experience
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Initialize Lucide icons
|
|
lucide.createIcons();
|
|
|
|
// Survey Start Tracking
|
|
let surveyStarted = false;
|
|
const surveyToken = "{{ survey.access_token }}";
|
|
|
|
function trackSurveyStart() {
|
|
if (surveyStarted) return;
|
|
|
|
fetch(`/surveys/s/${surveyToken}/track-start/`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
credentials: 'same-origin'
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.status === 'success') {
|
|
console.log('Survey started tracked:', data.survey_status);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error tracking survey start:', error);
|
|
});
|
|
|
|
surveyStarted = true;
|
|
}
|
|
|
|
// Rating Stars
|
|
function selectRating(star, fieldId) {
|
|
const value = star.getAttribute('data-value');
|
|
const stars = star.parentElement.querySelectorAll('.rating-star');
|
|
|
|
stars.forEach((s, index) => {
|
|
if (index < value) {
|
|
s.classList.add('active');
|
|
} else {
|
|
s.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
document.getElementById(fieldId).value = value;
|
|
updateProgress();
|
|
}
|
|
|
|
// NPS Selection
|
|
function selectNPS(button, fieldId) {
|
|
const value = button.getAttribute('data-value');
|
|
const buttons = button.parentElement.querySelectorAll('.nps-button');
|
|
|
|
buttons.forEach(b => b.classList.remove('selected'));
|
|
button.classList.add('selected');
|
|
|
|
document.getElementById(fieldId).value = value;
|
|
updateProgress();
|
|
}
|
|
|
|
// Likert Selection
|
|
function selectLikert(radio) {
|
|
const options = radio.closest('.likert-option').parentElement.querySelectorAll('.likert-option');
|
|
options.forEach(opt => opt.classList.remove('selected'));
|
|
radio.closest('.likert-option').classList.add('selected');
|
|
updateProgress();
|
|
}
|
|
|
|
// Yes/No Selection
|
|
function selectYesNo(button, fieldId) {
|
|
const value = button.getAttribute('data-value');
|
|
const buttons = button.parentElement.querySelectorAll('.yes-no-button');
|
|
|
|
buttons.forEach(b => b.classList.remove('selected'));
|
|
button.classList.add('selected');
|
|
|
|
document.getElementById(fieldId).value = value;
|
|
updateProgress();
|
|
}
|
|
|
|
// Update Progress Bar
|
|
function updateProgress() {
|
|
const form = document.getElementById('surveyForm');
|
|
const totalQuestions = '{{ total_questions }}';
|
|
const answeredQuestions = form.querySelectorAll('input[type="hidden"]:not([value=""]), input[type="radio"]:checked, input[type="text"]:not([value=""]), textarea:not([value=""])').length;
|
|
|
|
const progress = (answeredQuestions / totalQuestions) * 100;
|
|
document.getElementById('progressBar').style.width = progress + '%';
|
|
}
|
|
|
|
// Track first interaction with form
|
|
const surveyForm = document.getElementById('surveyForm');
|
|
|
|
// Track when user first interacts with any question
|
|
['click', 'input', 'change'].forEach(eventType => {
|
|
surveyForm.addEventListener(eventType, function(e) {
|
|
// Only track if it's a question interaction
|
|
const questionCard = e.target.closest('.question-card');
|
|
if (questionCard && !surveyStarted) {
|
|
trackSurveyStart();
|
|
}
|
|
}, { once: true });
|
|
});
|
|
|
|
// Form Submission
|
|
document.getElementById('surveyForm').addEventListener('submit', function(e) {
|
|
// Validate all required fields
|
|
const requiredFields = this.querySelectorAll('[required]');
|
|
let allValid = true;
|
|
|
|
requiredFields.forEach(field => {
|
|
if (!field.value) {
|
|
allValid = false;
|
|
field.closest('.question-card').style.borderLeft = '4px solid #ef4444';
|
|
} else {
|
|
field.closest('.question-card').style.borderLeft = 'none';
|
|
}
|
|
});
|
|
|
|
if (!allValid) {
|
|
e.preventDefault();
|
|
alert('{% if language == "ar" %}يرجى الإجابة على جميع الأسئلة المطلوبة{% else %}Please answer all required questions{% endif %}');
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
}
|
|
});
|
|
|
|
// Initialize progress on page load
|
|
updateProgress();
|
|
</script>
|
|
</body>
|
|
</html> |