feat: enhance UI/UX with improved styling and DataTables integration

- Add DataTables library integration for enhanced table functionality
- Improve navigation bar styling with rounded corners and white background
- Fix mobile button widths for better responsive design
- Enhance badge styling with proper text wrapping and ellipsis handling
- Add table sorting styles for projects and tasks lists
- Improve mobile responsiveness across various screen sizes
- Update filter button styling for better visual consistency
This commit is contained in:
Dries Peeters
2025-09-18 09:38:57 +02:00
parent 544dc5867e
commit fcb2508695
5 changed files with 146 additions and 40 deletions

View File

@@ -1542,7 +1542,7 @@ main {
.navbar-nav.ms-auto { flex: 0 0 auto; }
/* Allow wrapping to a second row on tighter widths (desktop too) */
.navbar-nav { flex-wrap: wrap; row-gap: 0.25rem; column-gap: 0.25rem; }
.navbar-nav { flex-wrap: wrap; row-gap: 0.25rem; column-gap: 0.25rem; background-color: white; border-bottom-left-radius: 25px; border-bottom-right-radius: 25px;}
.navbar-nav .nav-item { flex: 0 1 auto; min-width: 0; }
/* body attribute now handled by CSS vars above */

View File

@@ -568,7 +568,7 @@
}
.mobile-btn {
width: 100%;
width: auto;
margin-bottom: 0.75rem;
padding: 1rem 1.5rem;
font-size: 1rem;

View File

@@ -23,6 +23,12 @@
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- jQuery (required for DataTables) -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- DataTables CSS and JS -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css">
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
<!-- Custom CSS (cache-busted with app_version) -->

View File

@@ -107,7 +107,7 @@
<h6 class="mb-0">
<i class="fas fa-filter me-2 text-muted"></i>{{ _('Filter Tasks') }}
</h6>
<button type="button" class="btn btn-sm btn-outline-primary" id="toggleFilters" onclick="toggleFilterVisibility()" title="{{ _('Toggle Filters') }}">
<button type="button" class="btn btn-sm" id="toggleFilters" onclick="toggleFilterVisibility()" title="{{ _('Toggle Filters') }}">
<i class="fas fa-chevron-up" id="filterToggleIcon"></i>
</button>
</div>
@@ -327,6 +327,12 @@
position: relative;
overflow: hidden;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* Prevent text wrapping and ensure badge fits content */
white-space: nowrap;
display: inline-block;
flex-shrink: 0;
min-width: fit-content;
max-width: none;
}
.status-badge::before {
@@ -394,7 +400,6 @@
.priority-badge::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
@@ -772,6 +777,10 @@
.status-badge, .priority-badge {
font-size: 0.7rem;
padding: 0.2rem 0.6rem;
/* Ensure badges don't wrap text on medium screens */
white-space: nowrap;
flex-shrink: 0;
min-width: fit-content;
}
.task-description {
@@ -791,6 +800,14 @@
.status-badge, .priority-badge {
font-size: 0.65rem;
padding: 0.15rem 0.5rem;
/* Ensure badges don't wrap text on small screens */
white-space: nowrap;
flex-shrink: 0;
min-width: fit-content;
/* Slightly reduce font size for very long text */
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
}
.task-title-link {

View File

@@ -346,6 +346,21 @@
.filter-toggle-transition {
transition: all 0.3s ease-in-out;
}
/* Table sorting styles */
thead th[style*="cursor: pointer"]:hover {
background-color: var(--bs-gray-100);
}
thead th.sorted-asc::after {
content: " ▲";
color: var(--bs-primary);
}
thead th.sorted-desc::after {
content: " ▼";
color: var(--bs-primary);
}
</style>
<script type="application/json" id="i18n-json-projects-list">
@@ -403,31 +418,8 @@ document.addEventListener('DOMContentLoaded', function() {
filterBody.classList.add('filter-toggle-transition');
}, 100);
// Initialize DataTable
const table = $('#projectsTable').DataTable({
order: [[0, 'asc']],
pageLength: 25,
language: {
search: i18nProjects.search_projects || 'Search projects:',
lengthMenu: i18nProjects.length_menu || 'Show _MENU_ projects per page',
info: i18nProjects.info || 'Showing _START_ to _END_ of _TOTAL_ projects'
},
responsive: true,
dom: '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>' +
'<"row"<"col-sm-12"tr>>' +
'<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
columnDefs: [
{ orderable: false, targets: -1 }
]
});
// Custom search
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.addEventListener('keyup', function() {
table.search(this.value).draw();
});
}
// Initialize vanilla JavaScript table functionality
initializeProjectsTable();
// Fill progress bars
document.querySelectorAll('#projectsTable .progress-bar').forEach(el => {
@@ -436,18 +428,109 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
function filterByStatus(status) {
const table = $('#projectsTable').DataTable();
if (status === 'all') {
table.column(2).search('').draw();
} else {
// Match the rendered localized badge text
const activeLabel = i18nProjects.status_active || 'Active';
const archivedLabel = i18nProjects.status_archived || 'Archived';
const label = status === 'active' ? activeLabel : archivedLabel;
const regex = `^${label}$`;
table.column(2).search(regex, true, false).draw();
// Vanilla JavaScript table functionality
function initializeProjectsTable() {
const table = document.getElementById('projectsTable');
const searchInput = document.getElementById('searchInput');
if (!table) return;
// Store original rows for filtering
const tbody = table.querySelector('tbody');
const originalRows = Array.from(tbody.querySelectorAll('tr'));
// Search functionality
if (searchInput) {
searchInput.addEventListener('keyup', function() {
const searchTerm = this.value.toLowerCase();
filterTable(searchTerm, 'all');
});
}
// Table sorting functionality
const headers = table.querySelectorAll('thead th');
headers.forEach((header, index) => {
// Skip the actions column (last column)
if (index === headers.length - 1) return;
header.style.cursor = 'pointer';
header.addEventListener('click', () => sortTable(index));
});
function filterTable(searchTerm, statusFilter) {
const rows = tbody.querySelectorAll('tr');
rows.forEach(row => {
const cells = row.querySelectorAll('td');
let textMatch = false;
let statusMatch = true;
// Check text match in project name and description
if (cells[0]) {
const projectText = cells[0].textContent.toLowerCase();
textMatch = searchTerm === '' || projectText.includes(searchTerm);
}
// Check status match
if (statusFilter !== 'all' && cells[2]) {
const statusText = cells[2].textContent.toLowerCase();
const activeLabel = (i18nProjects.status_active || 'Active').toLowerCase();
const archivedLabel = (i18nProjects.status_archived || 'Archived').toLowerCase();
if (statusFilter === 'active') {
statusMatch = statusText.includes(activeLabel);
} else if (statusFilter === 'archived') {
statusMatch = statusText.includes(archivedLabel);
}
}
row.style.display = (textMatch && statusMatch) ? '' : 'none';
});
}
function sortTable(columnIndex) {
const rows = Array.from(tbody.querySelectorAll('tr'));
const isAscending = !table.dataset.sortAsc || table.dataset.sortAsc === 'false';
rows.sort((a, b) => {
const aText = a.cells[columnIndex].textContent.trim();
const bText = b.cells[columnIndex].textContent.trim();
// Try to parse as numbers for numeric columns
const aNum = parseFloat(aText.replace(/[^\d.-]/g, ''));
const bNum = parseFloat(bText.replace(/[^\d.-]/g, ''));
if (!isNaN(aNum) && !isNaN(bNum)) {
return isAscending ? aNum - bNum : bNum - aNum;
} else {
return isAscending ? aText.localeCompare(bText) : bText.localeCompare(aText);
}
});
// Clear and re-append sorted rows
tbody.innerHTML = '';
rows.forEach(row => tbody.appendChild(row));
// Update sort indicator
table.dataset.sortAsc = isAscending.toString();
// Update header indicators
headers.forEach(h => h.classList.remove('sorted-asc', 'sorted-desc'));
headers[columnIndex].classList.add(isAscending ? 'sorted-asc' : 'sorted-desc');
}
// Store the filter function for external use
window.filterProjectsTable = filterTable;
}
function filterByStatus(status) {
const searchInput = document.getElementById('searchInput');
const searchTerm = searchInput ? searchInput.value.toLowerCase() : '';
if (window.filterProjectsTable) {
window.filterProjectsTable(searchTerm, status);
}
document.querySelectorAll('.dropdown-item').forEach(i => i.classList.remove('active'));
const active = document.querySelector(`[onclick="filterByStatus('${status}')"]`);
if (active) active.classList.add('active');