Spaces:
Running
Running
| {% extends "base.html" %} | |
| {% block title %}Session Dashboard{% endblock %} | |
| {% block head %} | |
| <style> | |
| .name-column { | |
| word-wrap: break-word; | |
| white-space: normal; | |
| max-width: 250px; | |
| } | |
| @media (max-width: 768px) { | |
| .name-column { | |
| max-width: 25%; | |
| } | |
| } | |
| .table-responsive { | |
| overflow-x: auto; | |
| } | |
| th:first-child, td:first-child { | |
| position: sticky; | |
| left: 0; | |
| z-index: 2; | |
| background-color: var(--bg-dark); | |
| } | |
| th:nth-child(2), td:nth-child(2) { | |
| position: sticky; | |
| left: 40px; | |
| z-index: 2; | |
| background-color: var(--bg-dark); | |
| } | |
| /* Size column styling */ | |
| .size-column { | |
| font-weight: 600; | |
| color: #4db8ff; | |
| } | |
| </style> | |
| {% endblock %} | |
| {% block content %} | |
| <div class="container-fluid mt-4" style="width: 90%; margin: auto;"> | |
| <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3"> | |
| <h2>Session Dashboard</h2> | |
| <div class="d-flex gap-2"> | |
| <button id="batch-group-btn" class="btn btn-outline-info" style="display: none;">Set Group</button> | |
| <button id="delete-selected-btn" class="btn btn-danger" style="display: none;">Delete Selected</button> | |
| </div> | |
| </div> | |
| <!-- Filter tabs --> | |
| <ul class="nav nav-pills mb-3"> | |
| <li class="nav-item"> | |
| <a class="nav-link {{ 'active' if filter_type == 'all' else '' }}" href="{{ url_for('dashboard.dashboard') }}{% if show_size %}?size=1{% endif %}">All</a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link {{ 'active' if filter_type == 'standard' else '' }}" href="{{ url_for('dashboard.dashboard', filter='standard') }}{% if show_size %}&size=1{% endif %}">Standard Sessions</a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link {{ 'active' if filter_type == 'collections' else '' }}" href="{{ url_for('dashboard.dashboard', filter='collections') }}{% if show_size %}&size=1{% endif %}"> | |
| <i class="bi bi-bookmark-fill me-1"></i>NEETprep Collections | |
| </a> | |
| </li> | |
| </ul> | |
| <div class="table-responsive"> | |
| <table class="table table-hover table-striped"> | |
| <thead> | |
| <tr> | |
| <th scope="col" style="width: 3%;">S.No.</th> | |
| <th scope="col" style="width: 3%;"><input type="checkbox" id="select-all-checkbox"></th> | |
| <th scope="col" class="name-column">Name</th> | |
| <th scope="col">Group</th> | |
| <th scope="col">Created At</th> | |
| <th scope="col">Pages</th> | |
| <th scope="col">{{ 'Bookmarks' if filter_type == 'collections' else 'Questions' }}</th> | |
| {% if show_size %} | |
| <th scope="col">Size</th> | |
| {% endif %} | |
| <th scope="col">Status</th> | |
| <th scope="col">Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {% for session in sessions %} | |
| <tr data-session-id="{{ session.id }}"> | |
| <td>{{ loop.index }}</td> | |
| <td><input type="checkbox" class="session-checkbox"></td> | |
| <td class="editable-name name-column"> | |
| <span class="session-name">{{ session.name or session.original_filename }}</span> | |
| <i class="fas fa-pencil-alt edit-name-icon ms-2" style="cursor: pointer;"></i> | |
| <input type="text" class="form-control form-control-sm d-none" value="{{ session.name or session.original_filename }}"> | |
| </td> | |
| <td class="editable-group"> | |
| <span class="group-name text-muted small">{{ session.group_name or 'None' }}</span> | |
| <i class="fas fa-pencil-alt edit-group-icon ms-2" style="cursor: pointer; font-size: 0.8rem;"></i> | |
| <input type="text" class="form-control form-control-sm d-none" value="{{ session.group_name or '' }}" placeholder="Group Name"> | |
| </td> | |
| <td><span class="badge bg-secondary">{{ session.created_at | humanize }}</span></td> | |
| <td>{{ session.page_count }}</td> | |
| <td>{{ session.question_count }}</td> | |
| {% if show_size %} | |
| <td class="size-column">{{ session.total_size_formatted }}</td> | |
| {% endif %} | |
| <td> | |
| {% if session.persist %} | |
| <span class="badge bg-success">Persisted</span> | |
| {% else %} | |
| <span class="badge bg-secondary">Not Persisted</span> | |
| {% endif %} | |
| </td> | |
| <td> | |
| <div class="btn-group" role="group"> | |
| {% if session.session_type == 'neetprep_collection' %} | |
| <a href="{{ url_for('neetprep_bp.view_collection', session_id=session.id) }}" class="btn btn-sm btn-warning text-dark"><i class="bi bi-bookmark-fill me-1"></i>View</a> | |
| <button class="btn btn-sm btn-success generate-collection-pdf-btn" data-session-id="{{ session.id }}"><i class="bi bi-file-pdf me-1"></i>PDF</button> | |
| <button class="btn btn-sm btn-info toggle-persist-btn">Toggle Persist</button> | |
| {% elif session.session_type == 'color_rm' %} | |
| <a href="{{ url_for('main.color_rm_interface', session_id=session.id, image_index=0) }}" class="btn btn-sm btn-warning text-dark">Color RM</a> | |
| <button class="btn btn-sm btn-info toggle-persist-btn">Toggle Persist</button> | |
| {% if show_size %} | |
| <button class="btn btn-sm btn-warning reduce-space-btn">Reduce Space</button> | |
| {% endif %} | |
| {% else %} | |
| <a href="{{ url_for('main.question_entry_v2', session_id=session.id) }}" class="btn btn-sm btn-primary">View</a> | |
| <a href="{{ url_for('main.crop_interface_v2', session_id=session.id, image_index=0) }}" class="btn btn-sm btn-secondary">Crop</a> | |
| <button class="btn btn-sm btn-outline-success duplicate-session-btn" data-session-id="{{ session.id }}" title="Duplicate as NEETprep Collection"><i class="bi bi-copy"></i></button> | |
| <button class="btn btn-sm btn-info toggle-persist-btn">Toggle Persist</button> | |
| {% if show_size %} | |
| <button class="btn btn-sm btn-warning reduce-space-btn">Reduce Space</button> | |
| {% endif %} | |
| {% endif %} | |
| </div> | |
| </td> | |
| </tr> | |
| {% else %} | |
| {% if show_size %} | |
| <tr> | |
| <td colspan="10" class="text-center">No sessions found.</td> | |
| </tr> | |
| {% else %} | |
| <tr> | |
| <td colspan="9" class="text-center">No sessions found.</td> | |
| </tr> | |
| {% endif %} | |
| {% endfor %} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| {% endblock %} | |
| {% block scripts %} | |
| {{ super() }} | |
| <script> | |
| $(document).ready(function() { | |
| // Checkbox selection logic | |
| const deleteBtn = $('#delete-selected-btn'); | |
| const batchGroupBtn = $('#batch-group-btn'); | |
| const selectAllCheckbox = $('#select-all-checkbox'); | |
| const sessionCheckboxes = $('.session-checkbox'); | |
| function toggleDeleteButton() { | |
| const anyChecked = sessionCheckboxes.is(':checked'); | |
| deleteBtn.css('display', anyChecked ? 'inline-block' : 'none'); | |
| batchGroupBtn.css('display', anyChecked ? 'inline-block' : 'none'); | |
| } | |
| selectAllCheckbox.on('change', function() { | |
| sessionCheckboxes.prop('checked', $(this).prop('checked')); | |
| toggleDeleteButton(); | |
| }); | |
| sessionCheckboxes.on('change', function() { | |
| if (!$(this).prop('checked')) { | |
| selectAllCheckbox.prop('checked', false); | |
| } | |
| toggleDeleteButton(); | |
| }); | |
| // Batch delete | |
| deleteBtn.on('click', function() { | |
| const selectedIds = []; | |
| sessionCheckboxes.filter(':checked').each(function() { | |
| selectedIds.push($(this).closest('tr').data('session-id')); | |
| }); | |
| if (selectedIds.length > 0 && confirm('Are you sure you want to delete the selected sessions?')) { | |
| $.ajax({ | |
| url: '/sessions/batch_delete', | |
| type: 'POST', | |
| contentType: 'application/json', | |
| data: JSON.stringify({ ids: selectedIds }), | |
| success: function(response) { | |
| if (response.success) { | |
| location.reload(); | |
| } else { | |
| alert('Error deleting sessions: ' + response.error); | |
| } | |
| } | |
| }); | |
| } | |
| }); | |
| // Batch update group | |
| batchGroupBtn.on('click', function() { | |
| const selectedIds = []; | |
| sessionCheckboxes.filter(':checked').each(function() { | |
| selectedIds.push($(this).closest('tr').data('session-id')); | |
| }); | |
| const newGroup = prompt(`Enter group name for ${selectedIds.length} sessions (leave empty to remove group):`); | |
| if (newGroup !== null) { | |
| $.ajax({ | |
| url: '/sessions/batch_update_group', | |
| type: 'POST', | |
| contentType: 'application/json', | |
| data: JSON.stringify({ ids: selectedIds, group_name: newGroup.trim() || null }), | |
| success: function(response) { | |
| if (response.success) { | |
| location.reload(); | |
| } else { | |
| alert('Error updating groups: ' + response.error); | |
| } | |
| } | |
| }); | |
| } | |
| }); | |
| // Editable name | |
| $('.edit-name-icon').on('click', function() { | |
| const icon = $(this); | |
| const span = icon.prev('.session-name'); | |
| const input = icon.next('input'); | |
| span.addClass('d-none'); | |
| icon.addClass('d-none'); | |
| input.removeClass('d-none').focus(); | |
| }); | |
| $('.editable-name input').on('blur keyup', function(e) { | |
| if (e.type === 'blur' || e.key === 'Enter') { | |
| const input = $(this); | |
| const span = input.siblings('.session-name'); | |
| const icon = input.siblings('.edit-name-icon'); | |
| const newName = input.val(); | |
| const oldName = span.text(); | |
| const sessionId = input.closest('tr').data('session-id'); | |
| if (newName && newName !== oldName) { | |
| $.ajax({ | |
| url: `/rename_session/${sessionId}`, | |
| type: 'POST', | |
| contentType: 'application/json', | |
| data: JSON.stringify({ new_name: newName }), | |
| success: function(response) { | |
| if (response.success) { | |
| span.text(newName); | |
| } else { | |
| alert('Error renaming session: ' + response.error); | |
| input.val(oldName); // Revert on error | |
| } | |
| } | |
| }); | |
| } | |
| input.addClass('d-none'); | |
| span.removeClass('d-none'); | |
| icon.removeClass('d-none'); | |
| } | |
| }); | |
| // Group editing logic | |
| $('.edit-group-icon').on('click', function() { | |
| const icon = $(this); | |
| const span = icon.prev('.group-name'); | |
| const input = icon.next('input'); | |
| span.addClass('d-none'); | |
| icon.addClass('d-none'); | |
| input.removeClass('d-none').focus(); | |
| }); | |
| $('.editable-group input').on('blur keyup', function(e) { | |
| if (e.type === 'blur' || e.key === 'Enter') { | |
| const input = $(this); | |
| const span = input.siblings('.group-name'); | |
| const icon = input.siblings('.edit-group-icon'); | |
| const newGroup = input.val().trim(); | |
| const oldGroup = span.text() === 'None' ? '' : span.text(); | |
| const sessionId = input.closest('tr').data('session-id'); | |
| if (newGroup !== oldGroup) { | |
| $.ajax({ | |
| url: '/sessions/update_group', | |
| type: 'POST', | |
| contentType: 'application/json', | |
| data: JSON.stringify({ session_id: sessionId, group_name: newGroup }), | |
| success: function(response) { | |
| if (response.success) { | |
| span.text(newGroup || 'None'); | |
| } else { | |
| alert('Error updating group: ' + response.error); | |
| input.val(oldGroup); | |
| } | |
| } | |
| }); | |
| } | |
| input.addClass('d-none'); | |
| span.removeClass('d-none'); | |
| icon.removeClass('d-none'); | |
| } | |
| }); | |
| // Toggle persist | |
| $('.toggle-persist-btn').on('click', function() { | |
| const button = $(this); | |
| const sessionId = button.closest('tr').data('session-id'); | |
| $.ajax({ | |
| url: `/toggle_persist/${sessionId}`, | |
| type: 'POST', | |
| success: function(response) { | |
| if (response.success) { | |
| location.reload(); // Simple reload for now | |
| } else { | |
| alert('Error toggling persistence: ' + response.error); | |
| } | |
| } | |
| }); | |
| }); | |
| // Reduce space - only bind event if the button exists | |
| {% if show_size %} | |
| $('.reduce-space-btn').on('click', function() { | |
| const button = $(this); | |
| const row = button.closest('tr'); | |
| const sessionId = row.data('session-id'); | |
| if (confirm('Are you sure you want to reduce space? This will truncate the original page images to save disk space, but you will still be able to view questions and generate reports.')) { | |
| $.ajax({ | |
| url: `/sessions/reduce_space/${sessionId}`, | |
| type: 'POST', | |
| success: function(response) { | |
| if (response.success) { | |
| alert(response.message); | |
| // Refresh the page to update the size information | |
| location.reload(); | |
| } else { | |
| alert('Error reducing space: ' + response.error); | |
| } | |
| }, | |
| error: function(xhr) { | |
| const response = JSON.parse(xhr.responseText); | |
| alert('Error reducing space: ' + response.error); | |
| } | |
| }); | |
| } | |
| }); | |
| {% endif %} | |
| // Generate PDF from collection | |
| $('.generate-collection-pdf-btn').on('click', function() { | |
| const button = $(this); | |
| const sessionId = button.data('session-id'); | |
| button.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Generating...'); | |
| $.ajax({ | |
| url: `/neetprep/collections/${sessionId}/generate`, | |
| type: 'POST', | |
| contentType: 'application/json', | |
| data: JSON.stringify({}), | |
| success: function(response) { | |
| button.prop('disabled', false).html('<i class="bi bi-file-pdf me-1"></i>PDF'); | |
| if (response.success) { | |
| // Open PDF in new tab | |
| window.open(response.pdf_url, '_blank'); | |
| } else { | |
| alert('Error generating PDF: ' + (response.error || 'Unknown error')); | |
| } | |
| }, | |
| error: function(xhr) { | |
| button.prop('disabled', false).html('<i class="bi bi-file-pdf me-1"></i>PDF'); | |
| try { | |
| const response = JSON.parse(xhr.responseText); | |
| alert('Error generating PDF: ' + response.error); | |
| } catch(e) { | |
| alert('Error generating PDF'); | |
| } | |
| } | |
| }); | |
| }); | |
| // Duplicate session as collection | |
| $('.duplicate-session-btn').on('click', function() { | |
| const button = $(this); | |
| const sessionId = button.data('session-id'); | |
| if (confirm('Duplicate this session as a NEETprep collection? Only classified questions (subject + topic) will be included.')) { | |
| button.prop('disabled', true).html('<span class="spinner-border spinner-border-sm"></span>'); | |
| $.ajax({ | |
| url: `/neetprep/collections/duplicate/${sessionId}`, | |
| type: 'POST', | |
| success: function(response) { | |
| if (response.success) { | |
| alert(`Successfully duplicated! Created collection "${response.name}" with ${response.count} questions.`); | |
| location.reload(); | |
| } else { | |
| alert('Error duplicating session: ' + response.error); | |
| button.prop('disabled', false).html('<i class="bi bi-copy"></i>'); | |
| } | |
| }, | |
| error: function(xhr) { | |
| const err = xhr.responseJSON ? xhr.responseJSON.error : 'Unknown error'; | |
| alert('Error duplicating session: ' + err); | |
| button.prop('disabled', false).html('<i class="bi bi-copy"></i>'); | |
| } | |
| }); | |
| } | |
| }); | |
| }); | |
| </script> | |
| {% endblock %} | |