435 lines
28 KiB
Twig
435 lines
28 KiB
Twig
{% extends 'base.html.twig' %}
|
|
|
|
{% block title %}{{ question.title }} - پرسش و پاسخ{% endblock %}
|
|
|
|
{% block body %}
|
|
<main class="min-h-screen bg-gray-50">
|
|
<div class="container mx-auto px-4 py-8">
|
|
<div class="max-w-6xl mx-auto">
|
|
<!-- سوال -->
|
|
<div class="bg-white rounded-2xl shadow-soft mb-8 overflow-hidden">
|
|
<div class="p-8">
|
|
<div class="flex space-x-6 space-x-reverse">
|
|
<!-- سیستم رایدهی -->
|
|
<div class="flex flex-col items-center space-y-2 min-w-0">
|
|
<button class="vote-btn w-10 h-10 flex items-center justify-center rounded-lg border-2 border-green-200 text-green-600 hover:bg-green-50 hover:border-green-300 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
data-type="question"
|
|
data-id="{{ question.id }}"
|
|
data-upvote="true"
|
|
{% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
|
|
</svg>
|
|
</button>
|
|
<div class="text-2xl font-bold text-gray-900" id="question-votes-{{ question.id }}">
|
|
{{ question.votes }}
|
|
</div>
|
|
<button class="vote-btn w-10 h-10 flex items-center justify-center rounded-lg border-2 border-red-200 text-red-600 hover:bg-red-50 hover:border-red-300 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
data-type="question"
|
|
data-id="{{ question.id }}"
|
|
data-upvote="false"
|
|
{% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- محتوای سوال -->
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-start justify-between mb-6">
|
|
<h1 class="text-3xl font-bold text-gray-900 leading-tight">{{ question.title }}</h1>
|
|
{% if question.isSolved %}
|
|
<span class="inline-flex items-center space-x-1 space-x-reverse px-4 py-2 bg-green-100 text-green-800 text-sm font-medium rounded-full">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
</svg>
|
|
<span>حل شده</span>
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="prose prose-lg max-w-none text-gray-800 leading-relaxed mb-6">
|
|
{{ question.content|markdown|raw }}
|
|
</div>
|
|
|
|
<div class="flex flex-wrap items-center justify-between gap-4 pt-6 border-t border-gray-200">
|
|
<!-- تگها -->
|
|
<div class="flex flex-wrap gap-2">
|
|
{% for tagRelation in question.tagRelations %}
|
|
<a href="{{ path('qa_tag_questions', {'name': tagRelation.tag.name}) }}"
|
|
class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full hover:bg-blue-200 transition-colors duration-200">
|
|
{{ tagRelation.tag.name }}
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- اطلاعات سوال -->
|
|
<div class="flex items-center space-x-6 space-x-reverse text-sm text-gray-500">
|
|
<div class="flex items-center space-x-1 space-x-reverse">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
|
</svg>
|
|
<span>{{ question.author.name }}</span>
|
|
</div>
|
|
<div class="flex items-center space-x-1 space-x-reverse">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
<span>{{ question.createdAt|date('Y/m/d H:i') }}</span>
|
|
</div>
|
|
<div class="flex items-center space-x-1 space-x-reverse">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
|
</svg>
|
|
<span>{{ question.views }} بازدید</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- پاسخها -->
|
|
<div class="mb-8">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h2 class="text-2xl font-bold text-gray-900 flex items-center">
|
|
<svg class="w-6 h-6 text-blue-600 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
|
|
</svg>
|
|
پاسخها
|
|
<span class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm font-medium rounded-full mr-3">
|
|
{{ totalAnswers }}
|
|
</span>
|
|
</h2>
|
|
{% if app.user and 'ROLE_CUSTOMER' in app.user.roles %}
|
|
<a href="{{ path('qa_answer', {'id': question.id}) }}"
|
|
class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all duration-200 font-medium shadow-lg hover:shadow-xl transform hover:-translate-y-0.5">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
</svg>
|
|
<span>پاسخ دهید</span>
|
|
</a>
|
|
{% else %}
|
|
<a href="{{ path('customer_login') }}"
|
|
class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-all duration-200 font-medium">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path>
|
|
<polyline points="10,17 15,12 10,7"></polyline>
|
|
<line x1="15" y1="12" x2="3" y2="12"></line>
|
|
</svg>
|
|
<span>ورود برای پاسخ دادن</span>
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if answers is empty %}
|
|
<div class="bg-white rounded-2xl shadow-soft p-12 text-center">
|
|
<div class="max-w-md mx-auto">
|
|
<svg class="w-16 h-16 text-gray-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
|
|
</svg>
|
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">هنوز پاسخی داده نشده</h3>
|
|
<p class="text-gray-600">اولین کسی باشید که به این سوال پاسخ میدهد.</p>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="space-y-6">
|
|
{% for answer in answers %}
|
|
<div class="bg-white rounded-2xl shadow-soft overflow-hidden {{ answer.isAccepted ? 'ring-2 ring-green-200 border-green-200' : '' }}">
|
|
<div class="p-8">
|
|
<div class="flex space-x-6 space-x-reverse">
|
|
<!-- سیستم رایدهی پاسخ -->
|
|
<div class="flex flex-col items-center space-y-2 min-w-0">
|
|
<button class="vote-btn w-10 h-10 flex items-center justify-center rounded-lg border-2 border-green-200 text-green-600 hover:bg-green-50 hover:border-green-300 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
data-type="answer"
|
|
data-id="{{ answer.id }}"
|
|
data-upvote="true"
|
|
{% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
|
|
</svg>
|
|
</button>
|
|
<div class="text-2xl font-bold text-gray-900" id="answer-votes-{{ answer.id }}">
|
|
{{ answer.votes }}
|
|
</div>
|
|
<button class="vote-btn w-10 h-10 flex items-center justify-center rounded-lg border-2 border-red-200 text-red-600 hover:bg-red-50 hover:border-red-300 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
data-type="answer"
|
|
data-id="{{ answer.id }}"
|
|
data-upvote="false"
|
|
{% if not app.user or 'ROLE_CUSTOMER' not in app.user.roles %}disabled{% endif %}>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- محتوای پاسخ -->
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-start justify-between mb-4">
|
|
<div class="prose prose-lg max-w-none text-gray-800 leading-relaxed">
|
|
{{ answer.content|markdown|raw }}
|
|
</div>
|
|
{% if answer.isAccepted %}
|
|
<span class="inline-flex items-center space-x-1 space-x-reverse px-3 py-1 bg-green-100 text-green-800 text-sm font-medium rounded-full mr-4">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
</svg>
|
|
<span>پاسخ پذیرفته شده</span>
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between pt-4 border-t border-gray-200">
|
|
<div class="flex items-center space-x-4 space-x-reverse text-sm text-gray-500">
|
|
<div class="flex items-center space-x-1 space-x-reverse">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
|
</svg>
|
|
<span>{{ answer.author.name }}</span>
|
|
</div>
|
|
<div class="flex items-center space-x-1 space-x-reverse">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
<span>{{ answer.createdAt|date('Y/m/d H:i') }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
{% if app.user and app.user == question.author and not answer.isAccepted %}
|
|
<button class="accept-answer-btn inline-flex items-center space-x-2 space-x-reverse px-4 py-2 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition-colors duration-200 font-medium"
|
|
data-answer-id="{{ answer.id }}">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
</svg>
|
|
<span>پذیرفتن پاسخ</span>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- صفحهبندی پاسخها -->
|
|
{% if totalPages > 1 %}
|
|
<div class="mt-8">
|
|
<nav class="flex justify-center" aria-label="صفحهبندی پاسخها">
|
|
<div class="flex items-center space-x-2 space-x-reverse">
|
|
<!-- دکمه صفحه قبل -->
|
|
{% if currentPage > 1 %}
|
|
<a href="?page={{ currentPage - 1 }}"
|
|
class="flex items-center space-x-2 space-x-reverse px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
</svg>
|
|
<span>قبلی</span>
|
|
</a>
|
|
{% endif %}
|
|
|
|
<!-- شماره صفحات -->
|
|
<div class="flex items-center space-x-1 space-x-reverse">
|
|
{% for page in 1..totalPages %}
|
|
{% if page == currentPage %}
|
|
<span class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-blue-600 rounded-lg">
|
|
{{ page }}
|
|
</span>
|
|
{% elseif page <= currentPage + 2 and page >= currentPage - 2 %}
|
|
<a href="?page={{ page }}"
|
|
class="px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
|
|
{{ page }}
|
|
</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- دکمه صفحه بعد -->
|
|
{% if currentPage < totalPages %}
|
|
<a href="?page={{ currentPage + 1 }}"
|
|
class="flex items-center space-x-2 space-x-reverse px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 hover:text-gray-700 transition-all duration-200">
|
|
<span>بعدی</span>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
</svg>
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- دکمه بازگشت -->
|
|
<div class="text-center mt-8">
|
|
<a href="{{ path('qa_index') }}"
|
|
class="inline-flex items-center space-x-2 space-x-reverse px-6 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-all duration-200 font-medium">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
</svg>
|
|
<span>بازگشت به لیست سوالات</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
function initializeVoteButtons() {
|
|
// Wait for notification system to be available
|
|
if (typeof window.notification === 'undefined') {
|
|
console.log('Waiting for notification system...');
|
|
setTimeout(initializeVoteButtons, 100);
|
|
return;
|
|
}
|
|
|
|
// رایدهی
|
|
document.querySelectorAll('.vote-btn').forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
if (this.disabled) return;
|
|
|
|
const type = this.dataset.type;
|
|
const id = this.dataset.id;
|
|
const upvote = this.dataset.upvote === 'true';
|
|
|
|
fetch(`/qa/${type}/${id}/vote`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: `upvote=${upvote}&_token={{ csrf_token('vote') }}`
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
if (window.notification) {
|
|
window.notification.error(data.error);
|
|
}
|
|
return;
|
|
}
|
|
|
|
document.getElementById(`${type}-votes-${id}`).textContent = data.votes;
|
|
|
|
// تغییر رنگ دکمهها
|
|
const buttons = document.querySelectorAll(`[data-type="${type}"][data-id="${id}"]`);
|
|
buttons.forEach(btn => {
|
|
btn.classList.remove('bg-green-500', 'bg-red-500', 'border-green-500', 'border-red-500', 'text-white');
|
|
btn.classList.add(btn.dataset.upvote === 'true' ? 'border-green-200 text-green-600' : 'border-red-200 text-red-600');
|
|
});
|
|
|
|
if (data.userVote) {
|
|
const activeButton = document.querySelector(`[data-type="${type}"][data-id="${id}"][data-upvote="${data.userVote === 'up' ? 'true' : 'false'}"]`);
|
|
if (activeButton) {
|
|
activeButton.classList.remove('border-green-200', 'border-red-200', 'text-green-600', 'text-red-600');
|
|
activeButton.classList.add(data.userVote === 'up' ? 'bg-green-500 text-white border-green-500' : 'bg-red-500 text-white border-red-500');
|
|
}
|
|
if (window.notification) {
|
|
window.notification.success('رای شما ثبت شد');
|
|
}
|
|
} else {
|
|
// اگر رای حذف شده، همه دکمهها را به حالت عادی برگردان
|
|
buttons.forEach(btn => {
|
|
btn.classList.remove('bg-green-500', 'bg-red-500', 'text-white', 'border-green-500', 'border-red-500');
|
|
btn.classList.add(btn.dataset.upvote === 'true' ? 'border-green-200 text-green-600' : 'border-red-200 text-red-600');
|
|
});
|
|
if (window.notification) {
|
|
window.notification.info('رای شما حذف شد');
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
if (window.notification) {
|
|
window.notification.error('خطا در ارسال رای. لطفاً دوباره تلاش کنید.');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// پذیرفتن پاسخ
|
|
document.querySelectorAll('.accept-answer-btn').forEach(button => {
|
|
button.addEventListener('click', function() {
|
|
const answerId = this.dataset.answerId;
|
|
|
|
// Show confirmation dialog
|
|
const confirmed = confirm('آیا مطمئن هستید که میخواهید این پاسخ را بپذیرید؟');
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const originalText = this.innerHTML;
|
|
this.innerHTML = '<div class="loading-spinner"></div>';
|
|
this.disabled = true;
|
|
|
|
fetch(`/qa/answer/${answerId}/accept`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: `_token={{ csrf_token('accept') }}`
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
if (window.notification) {
|
|
window.notification.error(data.error);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (window.notification) {
|
|
window.notification.success('پاسخ با موفقیت پذیرفته شد');
|
|
}
|
|
setTimeout(() => {
|
|
location.reload();
|
|
}, 1500);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
if (window.notification) {
|
|
window.notification.error('خطا در پذیرفتن پاسخ. لطفاً دوباره تلاش کنید.');
|
|
}
|
|
})
|
|
.finally(() => {
|
|
// Reset button state
|
|
this.innerHTML = originalText;
|
|
this.disabled = false;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// اجرا در هر دو حالت
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Wait for notification system to be available
|
|
const checkNotification = () => {
|
|
if (window.notification) {
|
|
initializeVoteButtons();
|
|
} else {
|
|
setTimeout(checkNotification, 100);
|
|
}
|
|
};
|
|
checkNotification();
|
|
});
|
|
document.addEventListener('turbo:load', function() {
|
|
// Wait for notification system to be available
|
|
const checkNotification = () => {
|
|
if (window.notification) {
|
|
initializeVoteButtons();
|
|
} else {
|
|
setTimeout(checkNotification, 100);
|
|
}
|
|
};
|
|
checkNotification();
|
|
});
|
|
</script>
|
|
{% endblock %}
|