feat(ui): implement responsive mobile interface

- Add `relative` to nav for correct mobile dropdown positioning
- Increase hamburger button and mobile nav links to ≥44px touch targets
- Add mobile card view on files page (table hidden on small screens)
- Make bulk actions bar and pagination responsive/wrapping
- Add file type `accept` attribute and camera capture button on upload page
- Increase all interactive button/input sizes to ≥44px
- Add responsive CSS for pagination wrap and filter stacking at 480px"

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-23 19:09:49 +00:00
parent 54ab33ad98
commit d08e175352
4 changed files with 132 additions and 41 deletions
+54 -18
View File
@@ -420,26 +420,26 @@
<!-- Bulk Actions Section -->
<div id="bulkActionsBar" class="filters-section" style="display: none; background-color: #e6f3ff;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3">
<div>
<strong id="selectedCount">0</strong> files selected
</div>
<div style="display: flex; gap: 1rem;">
<button type="button" onclick="bulkReprocess()" style="padding: 0.5rem 1rem; background-color: #3182ce; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600;">
<div class="flex flex-wrap gap-2">
<button type="button" onclick="bulkReprocess()" style="padding: 0.5rem 1rem; background-color: #3182ce; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
<i class="fas fa-sync"></i> Reprocess Selected
</button>
<button type="button" onclick="bulkDelete()" style="padding: 0.5rem 1rem; background-color: #e53e3e; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600;">
<button type="button" onclick="bulkDelete()" style="padding: 0.5rem 1rem; background-color: #e53e3e; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
<i class="fas fa-trash"></i> Delete Selected
</button>
<button type="button" onclick="clearSelection()" style="padding: 0.5rem 1rem; background-color: #718096; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600;">
<button type="button" onclick="clearSelection()" style="padding: 0.5rem 1rem; background-color: #718096; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
Clear Selection
</button>
</div>
</div>
</div>
<!-- File table -->
<div class="overflow-x-auto">
<!-- File table (desktop) -->
<div class="hidden md:block overflow-x-auto">
<table class="file-table" id="fileTable">
<thead>
<tr>
@@ -518,10 +518,10 @@
<td>{{ file.created_at.strftime('%Y-%m-%d %H:%M:%S') if file.created_at else 'N/A' }}</td>
<td>
<div style="display: flex; align-items: center;">
<button onclick="viewFileDetail({{ file.id }}, event)" class="action-btn" title="View details">
<button onclick="viewFileDetail({{ file.id }}, event)" class="action-btn" title="View details" style="min-height:44px;min-width:44px;">
<i class="fas fa-info-circle"></i>
</button>
<button onclick="showDeleteModal({{ file.id }}, event)" class="action-btn delete" title="Delete file">
<button onclick="showDeleteModal({{ file.id }}, event)" class="action-btn delete" title="Delete file" style="min-height:44px;min-width:44px;">
<i class="fas fa-trash"></i>
</button>
</div>
@@ -536,29 +536,65 @@
</table>
</div>
<!-- Mobile card view (visible on small screens only) -->
<div class="md:hidden space-y-3" id="mobileCardView">
{% for file in files %}
<div
class="bg-white rounded-lg shadow p-4 cursor-pointer"
role="button"
tabindex="0"
onclick="viewFileDetail({{ file.id }}, event)"
onkeydown="if(event.key==='Enter'||event.key===' '){viewFileDetail({{ file.id }}, event);}"
aria-label="View details for {{ file.original_filename }}"
>
<div class="flex items-start justify-between gap-2">
<div class="flex-1 min-w-0">
<p class="font-semibold text-gray-800 truncate">{{ file.original_filename }}</p>
<p class="text-xs text-gray-500 mt-1">ID: {{ file.id }} &middot; {{ (file.file_size / 1024) | round(2) }} KB</p>
<p class="text-xs text-gray-500 mt-0.5">{{ file.mime_type }}</p>
<p class="text-xs text-gray-400 mt-0.5">{{ file.created_at.strftime('%Y-%m-%d %H:%M') if file.created_at else 'N/A' }}</p>
</div>
<div class="flex flex-col items-end gap-2 flex-shrink-0">
<span class="status-badge status-{{ file.processing_status }}">{{ file.processing_status | title }}</span>
<div class="flex gap-1" role="group" aria-label="File actions" onclick="event.stopPropagation();" onkeydown="event.stopPropagation();">
<button onclick="viewFileDetail({{ file.id }}, event)" class="action-btn" title="View details" style="min-height:44px;min-width:44px;">
<i class="fas fa-info-circle"></i>
</button>
<button onclick="showDeleteModal({{ file.id }}, event)" class="action-btn delete" title="Delete file" style="min-height:44px;min-width:44px;">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
{% else %}
<p class="text-center py-4 text-gray-500">No files found</p>
{% endfor %}
</div>
<!-- Pagination -->
{% if pagination.total_pages > 1 %}
<div class="pagination">
<div class="pagination flex-wrap gap-2">
<div class="pagination-info">
Showing {{ ((pagination.page - 1) * pagination.per_page + 1) }} -
{{ min(pagination.page * pagination.per_page, pagination.total_items) }}
of {{ pagination.total_items }} files
</div>
<div class="pagination-buttons">
<div class="pagination-buttons flex-wrap">
{% if pagination.page > 1 %}
<button class="pagination-button" onclick="goToPage(1)">First</button>
<button class="pagination-button" onclick="goToPage({{ pagination.page - 1 }})">Previous</button>
<button class="pagination-button" onclick="goToPage(1)" style="min-height:44px;">First</button>
<button class="pagination-button" onclick="goToPage({{ pagination.page - 1 }})" style="min-height:44px;">Previous</button>
{% endif %}
{% for p in range(max(1, pagination.page - 2), min(pagination.total_pages + 1, pagination.page + 3)) %}
<button class="pagination-button {% if p == pagination.page %}active{% endif %}" onclick="goToPage({{ p }})">
<button class="pagination-button {% if p == pagination.page %}active{% endif %}" onclick="goToPage({{ p }})" style="min-height:44px;">
{{ p }}
</button>
{% endfor %}
{% if pagination.page < pagination.total_pages %}
<button class="pagination-button" onclick="goToPage({{ pagination.page + 1 }})">Next</button>
<button class="pagination-button" onclick="goToPage({{ pagination.total_pages }})">Last</button>
<button class="pagination-button" onclick="goToPage({{ pagination.page + 1 }})" style="min-height:44px;">Next</button>
<button class="pagination-button" onclick="goToPage({{ pagination.total_pages }})" style="min-height:44px;">Last</button>
{% endif %}
</div>
</div>
@@ -570,8 +606,8 @@
<div class="modal-title">Confirm Deletion</div>
<p>Are you sure you want to delete this file?</p>
<div class="modal-buttons">
<button id="cancelDelete" class="modal-btn modal-btn-cancel">Cancel</button>
<button id="confirmDelete" class="modal-btn modal-btn-delete">Delete</button>
<button id="cancelDelete" class="modal-btn modal-btn-cancel" style="min-height:44px;min-width:80px;">Cancel</button>
<button id="confirmDelete" class="modal-btn modal-btn-delete" style="min-height:44px;min-width:80px;">Delete</button>
</div>
</div>
</div>