GestionTablets/templates/components/user_loans_results.html
ijuanes c9f3382c7c feat(ui): implement HTMX + Tailwind frontend revamp
- Add Tailwind CSS via CDN for modern styling
- Add HTMX for AJAX functionality without full page reloads
- Revamp user_loans interface with:
  - Real-time search (debounced 500ms)
  - Status filtering (all users, with loans, no loans)
  - Pagination (20 users per page)
  - Responsive table design
  - Clean, modern UI with Tailwind classes
- Create user_loans_detail.html for individual user loan history
- Update index.html with Tailwind styling
- Update base.html with Tailwind + HTMX setup
- Add components/user_loans_results.html for HTMX partial updates
- Update app.py user_loans route to support search, filter, pagination
- Add user_loans_detail route for detailed user view

This addresses the issues:
- Navigation overflow (fixed with responsive Tailwind classes)
- Basic look (modern, professional UI)
- User loans UX (fast search, filtering, pagination for 500+ users)
2026-06-20 08:38:42 +01:00

206 lines
9.8 KiB
HTML

{# templates/components/user_loans_results.html #}
{%- if users %}
{# Results Table Card #}
<div class="bg-white rounded-lg shadow overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-green-600 text-white">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
User
</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
Identification
</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
Active Loans
</th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
Last Loan Date
</th>
<th class="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{%- for user in users %}
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-6 py-4 whitespace-nowrap">
<div class="font-medium text-gray-900">{{ user.name }}</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-gray-500">
{{ user.identification or 'N/A' }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
{% if user.loan_count and user.loan_count > 0 %}bg-green-100 text-green-800
{% else %}bg-gray-100 text-gray-800
{% endif %}">
{{ user.loan_count or 0 }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ user.last_loan_date or 'Never' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm">
<a href="/user_loans/{{ user.id }}"
class="text-green-600 hover:text-green-800 font-medium">
View Details
</a>
</td>
</tr>
{%- endfor %}
</tbody>
</table>
</div>
{# Pagination #}
{%- if total_pages > 1 %}
<nav class="flex items-center justify-between pt-4" aria-label="Table navigation">
<div class="flex items-center space-x-2">
<span class="text-sm text-gray-500">
Showing <span class="font-medium text-gray-900">{{ (page - 1) * per_page + 1 }}</span>
to <span class="font-medium text-gray-900">{{ min(page * per_page, total_users) }}</span>
of <span class="font-medium text-gray-900">{{ total_users }}</span> users
</span>
</div>
<div class="flex items-center space-x-2">
{# Previous Button #}
{%- if page > 1 %}
<a
href="/user_loans?page={{ page - 1 }}&search={{ search }}&status={{ status }}"
hx-get="/user_loans?page={{ page - 1 }}&search={{ search }}&status={{ status }}"
hx-target="#results-container"
hx-swap="innerHTML"
hx-include="[name='search'], [name='status']"
class="px-3 py-2 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700"
>
Previous
</a>
{%- else %}
<span class="px-3 py-2 ml-0 leading-tight text-gray-300 bg-white border border-gray-300 rounded-l-lg cursor-not-allowed">
Previous
</span>
{%- endif %}
{# Page Numbers #}
{%- if total_pages <= 7 %}
{%- for p in range(1, total_pages + 1) %}
{%- if p == page %}
<span class="px-3 py-2 leading-tight text-white bg-green-600 border border-green-600 rounded">
{{ p }}
</span>
{%- else %}
<a
href="/user_loans?page={{ p }}&search={{ search }}&status={{ status }}"
hx-get="/user_loans?page={{ p }}&search={{ search }}&status={{ status }}"
hx-target="#results-container"
hx-swap="innerHTML"
hx-include="[name='search'], [name='status']"
class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700"
>
{{ p }}
</a>
{%- endif %}
{%- endfor %}
{%- else %}
{# Show first page #}
<a
href="/user_loans?page=1&search={{ search }}&status={{ status }}"
hx-get="/user_loans?page=1&search={{ search }}&status={{ status }}"
hx-target="#results-container"
hx-swap="innerHTML"
hx-include="[name='search'], [name='status']"
class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700"
>
1
</a>
{%- if page > 3 %}
<span class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300">...</span>
{%- endif %}
{# Show pages around current #}
{%- for p in range(max(2, page - 2), min(total_pages - 1, page + 2) + 1) %}
{%- if p == page %}
<span class="px-3 py-2 leading-tight text-white bg-green-600 border border-green-600 rounded">
{{ p }}
</span>
{%- else %}
<a
href="/user_loans?page={{ p }}&search={{ search }}&status={{ status }}"
hx-get="/user_loans?page={{ p }}&search={{ search }}&status={{ status }}"
hx-target="#results-container"
hx-swap="innerHTML"
hx-include="[name='search'], [name='status']"
class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700"
>
{{ p }}
</a>
{%- endif %}
{%- endfor %}
{%- if page < total_pages - 2 %}
<span class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300">...</span>
{%- endif %}
{# Show last page #}
<a
href="/user_loans?page={{ total_pages }}&search={{ search }}&status={{ status }}"
hx-get="/user_loans?page={{ total_pages }}&search={{ search }}&status={{ status }}"
hx-target="#results-container"
hx-swap="innerHTML"
hx-include="[name='search'], [name='status']"
class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700"
>
{{ total_pages }}
</a>
{%- endif %}
{# Next Button #}
{%- if page < total_pages %}
<a
href="/user_loans?page={{ page + 1 }}&search={{ search }}&status={{ status }}"
hx-get="/user_loans?page={{ page + 1 }}&search={{ search }}&status={{ status }}"
hx-target="#results-container"
hx-swap="innerHTML"
hx-include="[name='search'], [name='status']"
class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700"
>
Next
</a>
{%- else %}
<span class="px-3 py-2 leading-tight text-gray-300 bg-white border border-gray-300 rounded-r-lg cursor-not-allowed">
Next
</span>
{%- endif %}
</div>
</nav>
{%- endif %}
{%- else %}
{# No Results #}
<div class="bg-white rounded-lg shadow p-8 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<h3 class="text-lg font-medium text-gray-900 mb-2">No users found</h3>
<p class="text-gray-500 mb-4">
{% if search %}
No users match your search for "{{ search }}"
{% elif status == 'with_loans' %}
No users have active loans
{% elif status == 'no_loans' %}
All users have at least one active loan
{% else %}
There are no users in the system yet
{% endif %}
</p>
<a href="/user_loans"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 transition-colors">
Clear Filters
</a>
</div>
{%- endif %}