Merge pull request #373 from christianlouis/copilot/implement-responsive-mobile-interface
feat(ui): implement responsive mobile interface
This commit is contained in:
@@ -16,3 +16,23 @@ body {
|
||||
-webkit-mask-size: 100% 100%;
|
||||
mask-size: 100% 100%;
|
||||
}
|
||||
|
||||
/* Ensure pagination wraps properly on small screens */
|
||||
.pagination {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.pagination-buttons {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Ensure filter items stack on very small screens */
|
||||
@media (max-width: 480px) {
|
||||
.filter-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
.filter-item {
|
||||
min-width: unset;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<body class="bg-gray-50 min-h-screen flex flex-col">
|
||||
<!-- Global Nav -->
|
||||
<nav class="bg-white shadow">
|
||||
<nav class="bg-white shadow relative">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex justify-between h-16 items-center">
|
||||
|
||||
<!-- Brand + Icon -->
|
||||
@@ -104,7 +104,8 @@
|
||||
<button
|
||||
@click="mobileMenuOpen = !mobileMenuOpen"
|
||||
type="button"
|
||||
class="md:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
|
||||
class="md:hidden inline-flex items-center justify-center p-3 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
|
||||
style="min-height:44px;min-width:44px;"
|
||||
:aria-expanded="mobileMenuOpen"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
@@ -125,9 +126,9 @@
|
||||
class="md:hidden absolute top-16 inset-x-0 bg-white shadow-md z-50"
|
||||
>
|
||||
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3">
|
||||
<a href="/" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Home</a>
|
||||
<a href="/upload" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Upload</a>
|
||||
<a href="/files" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Files</a>
|
||||
<a href="/" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Home</a>
|
||||
<a href="/upload" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Upload</a>
|
||||
<a href="/files" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Files</a>
|
||||
|
||||
<!-- Admin section in mobile menu (shown only for admin users via JS) -->
|
||||
<div id="mobileAdminSection" class="hidden">
|
||||
@@ -135,30 +136,30 @@
|
||||
<p class="px-3 py-1 text-xs font-semibold text-red-600 uppercase tracking-wider flex items-center">
|
||||
<i class="fas fa-shield-alt mr-1"></i> Admin
|
||||
</p>
|
||||
<a href="/settings" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
|
||||
<a href="/settings" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
|
||||
<i class="fas fa-cog mr-2 text-gray-400"></i> Settings
|
||||
</a>
|
||||
<a href="/admin/credentials" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
|
||||
<a href="/admin/credentials" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
|
||||
<i class="fas fa-key mr-2 text-yellow-400"></i> Credentials
|
||||
</a>
|
||||
<a href="/env" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
|
||||
<a href="/env" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
|
||||
<i class="fas fa-terminal mr-2 text-gray-400"></i> Environment
|
||||
</a>
|
||||
<a href="/admin/files" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
|
||||
<a href="/admin/files" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
|
||||
<i class="fas fa-folder-open mr-2 text-gray-400"></i> File Manager
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status (de-emphasized) in mobile -->
|
||||
<a href="/status" class="block px-3 py-2 rounded-md text-sm font-medium text-gray-400 hover:text-gray-600 hover:bg-gray-50">
|
||||
<a href="/status" class="block px-3 py-3 rounded-md text-sm font-medium text-gray-400 hover:text-gray-600 hover:bg-gray-50">
|
||||
<i class="fas fa-circle-dot mr-1"></i> Status
|
||||
</a>
|
||||
|
||||
<a href="/about" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">About</a>
|
||||
<a href="/about" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">About</a>
|
||||
|
||||
<!-- Mobile Auth Section -->
|
||||
<div id="mobileAuthSection" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
|
||||
<div id="mobileAuthSection" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
|
||||
<!-- Will be populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 }} · {{ (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>
|
||||
|
||||
@@ -2,22 +2,25 @@
|
||||
{% block title %}Upload Document{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex flex-col items-center justify-center p-8">
|
||||
<h1 class="text-3xl font-bold mb-8">Upload Files</h1>
|
||||
<div class="flex flex-col items-center justify-center p-4 sm:p-8">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold mb-6 sm:mb-8">Upload Files</h1>
|
||||
|
||||
<!-- File Upload Section -->
|
||||
<div class="w-full max-w-2xl mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">Upload from Computer</h2>
|
||||
<div class="w-full max-w-2xl mb-6 sm:mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">Upload from Device</h2>
|
||||
<form action="/api/ui-upload" method="POST" enctype="multipart/form-data">
|
||||
<div
|
||||
id="dropZone"
|
||||
class="border-4 border-dashed border-gray-300 rounded-lg p-8 bg-white text-center w-full"
|
||||
class="border-4 border-dashed border-gray-300 rounded-lg p-6 sm:p-8 bg-white text-center w-full"
|
||||
ondrop="handleDrop(event)"
|
||||
ondragover="handleDragOver(event)"
|
||||
ondragleave="handleDragLeave(event)"
|
||||
>
|
||||
<p class="text-gray-500 mb-4">
|
||||
Drag & drop files here, or click to select files.
|
||||
<p class="text-gray-500 mb-4 hidden sm:block">
|
||||
Drag & drop files here, or click to select files.
|
||||
</p>
|
||||
<p class="text-gray-500 mb-4 sm:hidden">
|
||||
Tap to select files or use the camera button below.
|
||||
</p>
|
||||
<input
|
||||
id="fileInput"
|
||||
@@ -26,19 +29,47 @@
|
||||
onchange="handleFileSelect(event)"
|
||||
name="files"
|
||||
multiple
|
||||
accept="application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,image/*"
|
||||
/>
|
||||
<!-- Browse Files button (all devices) -->
|
||||
<button
|
||||
type="button"
|
||||
onclick="document.getElementById('fileInput').click()"
|
||||
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition duration-200 mb-4"
|
||||
style="min-height:44px;"
|
||||
>
|
||||
<i class="fas fa-folder-open mr-2"></i> Browse Files
|
||||
</button>
|
||||
<div class="text-sm text-gray-500 mt-2">
|
||||
<p>Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images</p>
|
||||
<p>Maximum size: 500MB per file</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Camera capture button - mobile only -->
|
||||
<div class="mt-3 sm:hidden">
|
||||
<label
|
||||
class="w-full flex items-center justify-center gap-2 bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-lg transition duration-200 cursor-pointer"
|
||||
style="min-height:44px;"
|
||||
>
|
||||
<i class="fas fa-camera"></i> Take Photo / Scan Document
|
||||
<input
|
||||
id="cameraInput"
|
||||
type="file"
|
||||
class="hidden"
|
||||
onchange="handleFileSelect(event)"
|
||||
accept="image/*"
|
||||
capture="environment"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- URL Upload Section -->
|
||||
<div class="w-full max-w-2xl mb-8">
|
||||
<div class="w-full max-w-2xl mb-6 sm:mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">Upload from URL</h2>
|
||||
<div class="bg-white border border-gray-300 rounded-lg p-6">
|
||||
<div class="bg-white border border-gray-300 rounded-lg p-4 sm:p-6">
|
||||
<form id="urlUploadForm" class="space-y-4">
|
||||
<div>
|
||||
<label for="urlInput" class="block text-sm font-medium text-gray-700 mb-2">
|
||||
@@ -49,7 +80,8 @@
|
||||
id="urlInput"
|
||||
name="url"
|
||||
placeholder="https://example.com/document.pdf"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
style="min-height:44px;"
|
||||
required
|
||||
/>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
@@ -65,7 +97,8 @@
|
||||
id="urlFilename"
|
||||
name="filename"
|
||||
placeholder="my-document.pdf"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
style="min-height:44px;"
|
||||
/>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
Leave empty to use filename from URL
|
||||
@@ -74,6 +107,7 @@
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200"
|
||||
style="min-height:44px;"
|
||||
>
|
||||
Download and Process
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user