GestionTablets/templates/user_loans.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

124 lines
5.2 KiB
HTML

{% extends "base.html" %}
{% block content %}
<div class="space-y-6">
<!-- Page Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<h2 class="text-xl font-semibold text-gray-800">User Loans</h2>
<div class="text-sm text-gray-500">
<span id="result-count" hx-swap-oob="true">{{ total_users }} users</span>
</div>
</div>
<!-- Search and Filters Card -->
<div class="bg-white rounded-lg shadow p-4">
<form id="search-form"
hx-get="/user_loans"
hx-target="#results-container"
hx-swap="innerHTML"
hx-include="[name='search'], [name='status'], [name='page']"
class="space-y-4">
<!-- Search Row -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- Search Input -->
<div class="md:col-span-2">
<label for="search" class="block text-sm font-medium text-gray-700 mb-1">
Search Users
</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"
fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
<input
type="text"
id="search"
name="search"
value="{{ search or '' }}"
placeholder="Search by name or identification..."
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-green-500 focus:border-green-500"
>
</div>
</div>
<!-- Status Filter -->
<div>
<label for="status" class="block text-sm font-medium text-gray-700 mb-1">
Loan Status
</label>
<select
id="status"
name="status"
class="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-green-500 focus:border-green-500"
>
<option value="" {% if not status %}selected{% endif %}>All Users</option>
<option value="with_loans" {% if status == 'with_loans' %}selected{% endif %}>With Active Loans</option>
<option value="no_loans" {% if status == 'no_loans' %}selected{% endif %}>No Active Loans</option>
</select>
</div>
</div>
<!-- Submit Button -->
<button type="submit"
class="w-full sm:w-auto px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
Search
</button>
</form>
</div>
<!-- Loading Indicator -->
<div id="loading" class="htmx-indicator hidden"></div>
<!-- Results Container -->
<div id="results-container">
{% include 'components/user_loans_results.html' %}
</div>
</div>
<!-- HTMX Script to handle URL updates -->
<script>
// Update browser URL when HTMX does a GET request
document.body.addEventListener('htmx:afterRequest', function(evt) {
if (evt.detail.requestConfig.method === 'get' && evt.detail.successful) {
// Update URL without reload
const url = new URL(window.location);
const form = document.getElementById('search-form');
if (form) {
const formData = new FormData(form);
for (let [key, value] of formData.entries()) {
if (value) {
url.searchParams.set(key, value);
} else {
url.searchParams.delete(key);
}
}
window.history.pushState({}, '', url);
}
}
});
// Restore form state from URL on page load
document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search);
const form = document.getElementById('search-form');
if (form) {
for (let [key, value] of urlParams.entries()) {
const input = form.querySelector(`[name="${key}"]`);
if (input) {
if (input.type === 'checkbox' || input.type === 'radio') {
input.checked = input.value === value;
} else {
input.value = value;
}
}
}
}
});
</script>
{% endblock %}