Report-Generator / templates /collection_view.html
t
style: unify UI/UX design system across all templates
106be3a
{% extends "base.html" %}
{% block title %}{{ collection.name or 'Collection' }}{% endblock %}
{% block head %}
<style>
body, html {
background-color: var(--bg-dark);
}
.collection-header {
background: linear-gradient(135deg, var(--bg-dark), var(--bg-card));
border: 1px solid var(--bg-elevated);
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
}
.topic-section {
background-color: var(--bg-card);
border-radius: 12px;
margin-bottom: 15px;
overflow: hidden;
}
.topic-header {
background: linear-gradient(180deg, var(--bg-elevated), var(--bg-card));
padding: 12px 15px;
cursor: pointer;
transition: all var(--transition-fast);
}
.topic-header:hover {
background-color: var(--bg-hover);
}
.topic-body {
padding: 15px;
}
.question-card {
background-color: var(--bg-elevated);
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
position: relative;
transition: all var(--transition-fast);
}
.question-card:hover {
border-color: var(--accent-primary);
}
.question-card:last-child {
margin-bottom: 0;
}
.question-text {
font-size: 0.95rem;
color: var(--text-primary);
margin-bottom: 10px;
}
.option-list {
list-style-type: upper-alpha;
padding-left: 25px;
margin-bottom: 10px;
}
.option-list li {
padding: 4px 0;
color: var(--text-muted);
}
.option-list li.correct {
color: var(--accent-success);
font-weight: 600;
}
.question-meta {
font-size: 0.8rem;
color: var(--border-muted);
}
.remove-btn {
position: absolute;
top: 10px;
right: 10px;
opacity: 0.5;
transition: opacity var(--transition-fast);
}
.remove-btn:hover {
opacity: 1;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--border-muted);
}
.empty-state i {
font-size: 4rem;
margin-bottom: 20px;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid mt-4" style="width: 90%; margin: auto;">
<!-- Header -->
<div class="collection-header">
<div class="d-flex justify-content-between align-items-start flex-wrap gap-2">
<div>
<h2 class="mb-2">
<i class="bi bi-bookmark-fill text-warning me-2"></i>
<span id="collection-name">{{ collection.name or 'Untitled Collection' }}</span>
<button class="btn btn-link text-secondary btn-sm" id="edit-name-btn" title="Edit name">
<i class="bi bi-pencil"></i>
</button>
</h2>
<p class="text-muted mb-0">
<span class="badge bg-secondary">{{ question_count }} questions</span>
{% if collection.subject %}
<span class="badge bg-info">{{ collection.subject }}</span>
{% endif %}
{% if collection.tags %}
<span class="badge bg-primary">{{ collection.tags }}</span>
{% endif %}
</p>
</div>
<div class="d-flex gap-2">
{% if question_count > 0 %}
<button class="btn btn-primary btn-pill" id="start-quiz-btn">
<i class="bi bi-play-fill me-1"></i>Start Quiz
</button>
{% endif %}
<button class="btn btn-success btn-pill" id="generate-pdf-btn">
<i class="bi bi-file-pdf me-1"></i>Generate PDF
</button>
<a href="{{ url_for('dashboard.dashboard', filter='collections') }}" class="btn btn-outline-secondary btn-pill">
<i class="bi bi-arrow-left me-1"></i>Back
</a>
</div>
</div>
</div>
<!-- Questions by Topic -->
{% if topics %}
{% for topic, questions_list in topics.items() %}
<div class="topic-section">
<div class="topic-header d-flex justify-content-between align-items-center" data-bs-toggle="collapse" data-bs-target="#topic-{{ loop.index }}">
<div>
<i class="bi bi-folder-fill text-warning me-2"></i>
<strong>{{ topic }}</strong>
<span class="badge bg-secondary ms-2">{{ questions_list|length }}</span>
</div>
<i class="bi bi-chevron-down"></i>
</div>
<div class="collapse show topic-body" id="topic-{{ loop.index }}">
{% for q in questions_list %}
<div class="question-card" data-question-id="{{ q.id }}" data-question-type="{{ q.question_type }}">
<button class="btn btn-outline-danger btn-sm remove-btn" title="Remove from collection">
<i class="bi bi-x-lg"></i>
</button>
<!-- Question Number Badge -->
<div class="d-flex justify-content-between align-items-start mb-2">
<span class="badge bg-dark">#{{ loop.index }}</span>
{% if q.correct_answer_index is not none %}
<span class="badge bg-success">Answer: {{ 'ABCDEFGH'[q.correct_answer_index|int] if q.correct_answer_index|int < 8 else q.correct_answer_index }}</span>
{% endif %}
</div>
{% if q.image_filename %}
<div class="question-image-container mb-2">
<img src="/processed/{{ q.image_filename }}" class="img-fluid rounded" alt="Question" style="max-height: 300px;">
</div>
{% else %}
<div class="question-text">{{ q.question_text or 'No question text available' }}</div>
{% endif %}
{% if q.options %}
<ol class="option-list">
{% for opt in q.options|from_json %}
<li class="{{ 'correct' if loop.index0 == q.correct_answer_index else '' }}">{{ opt }}</li>
{% endfor %}
</ol>
{% endif %}
<div class="question-meta d-flex flex-wrap gap-2 align-items-center">
{% if q.level %}
<span><i class="bi bi-speedometer2 me-1"></i>{{ q.level }}</span>
{% endif %}
<span><i class="bi bi-book me-1"></i>{{ q.subject or 'Unknown' }}</span>
<span class="badge {{ 'bg-info' if q.question_type == 'neetprep' else 'bg-success' }}">{{ q.question_type }}</span>
{% if q.question_number %}
<span><i class="bi bi-hash me-1"></i>Q{{ q.question_number }}</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% else %}
<div class="empty-state">
<i class="bi bi-bookmark"></i>
<h4>No Questions Yet</h4>
<p>Questions you bookmark during quizzes will appear here.</p>
<a href="{{ url_for('neetprep_bp.index') }}" class="btn btn-primary">
<i class="bi bi-play-fill me-1"></i>Start a Quiz
</a>
</div>
{% endif %}
</div>
<!-- Edit Name Modal -->
<div class="modal fade" id="editNameModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content bg-dark text-white">
<div class="modal-header border-secondary">
<h5 class="modal-title">Edit Collection Name</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="text" id="new-name-input" class="form-control bg-secondary text-white border-secondary"
value="{{ collection.name or '' }}" placeholder="Collection name...">
</div>
<div class="modal-footer border-secondary">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="save-name-btn">Save</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const sessionId = '{{ collection.id }}';
// Edit name
document.getElementById('edit-name-btn').onclick = () => {
new bootstrap.Modal(document.getElementById('editNameModal')).show();
};
document.getElementById('save-name-btn').onclick = async () => {
const newName = document.getElementById('new-name-input').value.trim();
if (!newName) return;
try {
const res = await fetch(`/neetprep/collections/${sessionId}/update`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: newName })
});
const data = await res.json();
if (data.success) {
document.getElementById('collection-name').textContent = newName;
bootstrap.Modal.getInstance(document.getElementById('editNameModal')).hide();
}
} catch(e) {
alert('Error updating name');
}
};
// Generate PDF
document.getElementById('generate-pdf-btn').onclick = async function() {
const btn = this;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Generating...';
try {
const res = await fetch(`/neetprep/collections/${sessionId}/generate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
});
const data = await res.json();
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-file-pdf me-1"></i>Generate PDF';
if (data.success && data.pdf_url) {
window.open(data.pdf_url, '_blank');
} else {
alert('Error: ' + (data.error || 'Failed to generate PDF'));
}
} catch(e) {
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-file-pdf me-1"></i>Generate PDF';
alert('Error generating PDF');
}
};
// Start Quiz
const quizBtn = document.getElementById('start-quiz-btn');
if (quizBtn) {
quizBtn.onclick = function() {
window.location.href = `/neetprep/collections/${sessionId}/quiz`;
};
}
// Remove from collection
document.querySelectorAll('.remove-btn').forEach(btn => {
btn.onclick = async function() {
const card = this.closest('.question-card');
const questionId = card.dataset.questionId;
const questionType = card.dataset.questionType;
if (!confirm('Remove this question from the collection?')) return;
try {
const res = await fetch('/neetprep/bookmark', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question_id: questionId, session_id: sessionId, question_type: questionType })
});
const data = await res.json();
if (data.success) {
card.remove();
// Update count in header
const countBadge = document.querySelector('.collection-header .badge.bg-secondary');
if (countBadge) {
const currentCount = parseInt(countBadge.textContent);
countBadge.textContent = `${currentCount - 1} questions`;
}
}
} catch(e) {
alert('Error removing question');
}
};
});
</script>
{% endblock %}