first
This commit is contained in:
196
templates/contacts/form.html
Normal file
196
templates/contacts/form.html
Normal file
@@ -0,0 +1,196 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
{% if title %}
|
||||
<h1 class="text-2xl font-bold text-gray-800 mb-6">{{ title }}</h1>
|
||||
{% else %}
|
||||
<h1 class="text-2xl font-bold text-gray-800 mb-6">User Form</h1>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('contacts.contacts_list') }}"
|
||||
class="text-gray-600 hover:text-gray-900">
|
||||
← Back to Contacts
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<form method="POST" class="space-y-6" enctype="multipart/form-data">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<!-- Profile Picture Upload (matches profile page style) -->
|
||||
<div class="flex flex-col items-center mb-6">
|
||||
<div class="relative group flex flex-col items-center">
|
||||
<label for="profile_picture" class="cursor-pointer">
|
||||
<img id="avatarPreview" src="{{ url_for('profile_pic', filename=form.profile_picture.data or user.profile_picture) if (form.profile_picture.data or (user and user.profile_picture)) else url_for('static', filename='default-avatar.png') }}" alt="Profile Picture" class="w-32 h-32 rounded-full object-cover border-4 border-gray-200 mb-0 transition duration-200 group-hover:opacity-80 group-hover:ring-4 group-hover:ring-primary-200 shadow-sm">
|
||||
<input id="profile_picture" type="file" name="profile_picture" accept="image/*" class="hidden" onchange="previewAvatar(event)" />
|
||||
</label>
|
||||
{% if user and user.profile_picture %}
|
||||
<button type="submit" name="remove_picture" value="1" class="mt-2 mb-2 text-xs px-3 py-1 rounded bg-red-100 text-red-700 border border-red-200 hover:bg-red-200 transition">Remove Picture</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
{{ form.first_name.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.first_name(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500") }}
|
||||
{% if form.first_name.errors %}
|
||||
{% for error in form.first_name.errors %}
|
||||
<p class="mt-1 text-sm text-red-600">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ form.last_name.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.last_name(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500") }}
|
||||
{% if form.last_name.errors %}
|
||||
{% for error in form.last_name.errors %}
|
||||
<p class="mt-1 text-sm text-red-600">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ form.email.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.email(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500") }}
|
||||
{% if form.email.errors %}
|
||||
{% for error in form.email.errors %}
|
||||
<p class="mt-1 text-sm text-red-600">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if current_user.is_admin %}
|
||||
<div class="relative">
|
||||
{{ form.new_password.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.new_password(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10", autocomplete="new-password", id="new_password") }}
|
||||
<button type="button" tabindex="-1" class="absolute right-2 top-9 text-gray-500" style="background: none; border: none;" onmousedown="showPwd('new_password')" onmouseup="hidePwd('new_password')" onmouseleave="hidePwd('new_password')">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative">
|
||||
{{ form.confirm_password.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.confirm_password(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 pr-10", autocomplete="new-password", id="confirm_password") }}
|
||||
<button type="button" tabindex="-1" class="absolute right-2 top-9 text-gray-500" style="background: none; border: none;" onmousedown="showPwd('confirm_password')" onmouseup="hidePwd('confirm_password')" onmouseleave="hidePwd('confirm_password')">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
{% if form.confirm_password.errors %}
|
||||
{% for error in form.confirm_password.errors %}
|
||||
<p class="mt-1 text-sm text-red-600">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<script>
|
||||
function showPwd(id) {
|
||||
document.getElementById(id).type = 'text';
|
||||
}
|
||||
function hidePwd(id) {
|
||||
document.getElementById(id).type = 'password';
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
{{ form.phone.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.phone(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500") }}
|
||||
{% if form.phone.errors %}
|
||||
{% for error in form.phone.errors %}
|
||||
<p class="mt-1 text-sm text-red-600">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ form.company.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.company(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500") }}
|
||||
{% if form.company.errors %}
|
||||
{% for error in form.company.errors %}
|
||||
<p class="mt-1 text-sm text-red-600">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ form.position.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.position(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500") }}
|
||||
{% if form.position.errors %}
|
||||
{% for error in form.position.errors %}
|
||||
<p class="mt-1 text-sm text-red-600">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ form.notes.label(class="block text-sm font-medium text-gray-700 mb-1") }}
|
||||
{{ form.notes(class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", rows="4") }}
|
||||
{% if form.notes.errors %}
|
||||
{% for error in form.notes.errors %}
|
||||
<p class="mt-1 text-sm text-red-600">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex items-center">
|
||||
{{ form.is_active(class="h-4 w-4 focus:ring-blue-500 border-gray-300 rounded", style="accent-color: #16767b;") }}
|
||||
{{ form.is_active.label(class="ml-2 block text-sm text-gray-900") }}
|
||||
</div>
|
||||
<div class="flex items-center relative group">
|
||||
{% set is_last_admin = current_user.is_admin and total_admins <= 1 %}
|
||||
{{ form.is_admin(
|
||||
class="h-4 w-4 focus:ring-blue-500 border-gray-300 rounded",
|
||||
style="accent-color: #16767b;",
|
||||
disabled=is_last_admin and form.is_admin.data
|
||||
) }}
|
||||
{{ form.is_admin.label(class="ml-2 block text-sm text-gray-900") }}
|
||||
{% if is_last_admin and form.is_admin.data %}
|
||||
<input type="hidden" name="is_admin" value="y">
|
||||
{% endif %}
|
||||
<div class="absolute left-0 bottom-full mb-2 hidden group-hover:block bg-gray-800 text-white text-xs rounded py-1 px-2 w-48">
|
||||
Admin users have full access to manage contacts and system settings.
|
||||
</div>
|
||||
{% if is_last_admin and form.is_admin.data %}
|
||||
<div class="ml-2 text-sm text-amber-600">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
You are the only admin
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if form.is_admin.errors %}
|
||||
<div class="p-3 bg-red-50 border border-red-200 rounded-lg">
|
||||
{% for error in form.is_admin.errors %}
|
||||
<p class="text-sm text-red-700">{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
{{ form.submit(class="text-white px-6 py-2 rounded-lg transition duration-200", style="background-color: #16767b; border: 1px solid #16767b;", onmouseover="this.style.backgroundColor='#1a8a90'", onmouseout="this.style.backgroundColor='#16767b'") }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function previewAvatar(event) {
|
||||
const [file] = event.target.files;
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
document.getElementById('avatarPreview').src = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
247
templates/contacts/list.html
Normal file
247
templates/contacts/list.html
Normal file
@@ -0,0 +1,247 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<style>
|
||||
body {
|
||||
background: #f7f9fb;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">Contacts</h1>
|
||||
<a href="{{ url_for('contacts.new_contact') }}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg text-white text-sm font-semibold transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg no-underline"
|
||||
style="background-color: #16767b; border: 1px solid #16767b;"
|
||||
onmouseover="this.style.backgroundColor='#1a8a90'"
|
||||
onmouseout="this.style.backgroundColor='#16767b'">
|
||||
<i class="fas fa-plus"></i>
|
||||
Add New Contact
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter Section -->
|
||||
<div class="bg-white rounded-lg shadow-sm p-4 mb-6">
|
||||
<form method="GET" class="flex flex-col md:flex-row gap-4" id="filterForm">
|
||||
<div class="flex-1">
|
||||
<input type="text" name="search" placeholder="Search contacts..."
|
||||
value="{{ request.args.get('search', '') }}"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-200">
|
||||
</div>
|
||||
<div class="flex gap-4 items-center">
|
||||
<select name="status" class="px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-200">
|
||||
<option value="">All Status</option>
|
||||
<option value="active" {% if request.args.get('status') == 'active' %}selected{% endif %}>Active</option>
|
||||
<option value="inactive" {% if request.args.get('status') == 'inactive' %}selected{% endif %}>Inactive</option>
|
||||
</select>
|
||||
<select name="role" class="px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-200">
|
||||
<option value="">All Roles</option>
|
||||
<option value="admin" {% if request.args.get('role') == 'admin' %}selected{% endif %}>Admin</option>
|
||||
<option value="user" {% if request.args.get('role') == 'user' %}selected{% endif %}>User</option>
|
||||
</select>
|
||||
<button type="button" id="clearFilters" class="px-4 py-2 rounded-lg text-white font-medium transition-colors duration-200"
|
||||
style="background-color: #16767b; border: 1px solid #16767b;"
|
||||
onmouseover="this.style.backgroundColor='#1a8a90'"
|
||||
onmouseout="this.style.backgroundColor='#16767b'">
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
// Debounce function
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function(...args) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||
};
|
||||
}
|
||||
// Auto-submit the form on select change
|
||||
document.querySelectorAll('#filterForm select').forEach(function(el) {
|
||||
el.addEventListener('change', function() {
|
||||
document.getElementById('filterForm').submit();
|
||||
});
|
||||
});
|
||||
// Debounced submit for search input, keep cursor after reload
|
||||
const searchInput = document.querySelector('#filterForm input[name="search"]');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', debounce(function() {
|
||||
// Save value and cursor position
|
||||
sessionStorage.setItem('searchFocus', '1');
|
||||
sessionStorage.setItem('searchValue', searchInput.value);
|
||||
sessionStorage.setItem('searchPos', searchInput.selectionStart);
|
||||
document.getElementById('filterForm').submit();
|
||||
}, 300));
|
||||
// On page load, restore focus and cursor position if needed
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
if (sessionStorage.getItem('searchFocus') === '1') {
|
||||
searchInput.focus();
|
||||
const val = sessionStorage.getItem('searchValue') || '';
|
||||
const pos = parseInt(sessionStorage.getItem('searchPos')) || val.length;
|
||||
searchInput.value = val;
|
||||
searchInput.setSelectionRange(pos, pos);
|
||||
// Clean up
|
||||
sessionStorage.removeItem('searchFocus');
|
||||
sessionStorage.removeItem('searchValue');
|
||||
sessionStorage.removeItem('searchPos');
|
||||
}
|
||||
});
|
||||
}
|
||||
// Clear button resets all filters and submits the form
|
||||
document.getElementById('clearFilters').addEventListener('click', function() {
|
||||
document.querySelector('#filterForm input[name="search"]').value = '';
|
||||
document.querySelector('#filterForm select[name="status"]').selectedIndex = 0;
|
||||
document.querySelector('#filterForm select[name="role"]').selectedIndex = 0;
|
||||
document.getElementById('filterForm').submit();
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<!-- Contacts List -->
|
||||
<div class="bg-white rounded-lg shadow-sm overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Contact Info</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Company</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th>
|
||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 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">
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 h-10 w-10">
|
||||
<img class="h-10 w-10 rounded-full object-cover"
|
||||
src="{{ url_for('profile_pic', filename=user.profile_picture) if user.profile_picture else url_for('static', filename='default-avatar.png') }}"
|
||||
alt="{{ user.username }}">
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="text-sm font-medium text-gray-900">{{ user.username }} {{ user.last_name }}</div>
|
||||
<div class="text-sm text-gray-500">{{ user.position or 'No position' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex flex-row flex-wrap gap-1.5">
|
||||
<a href="mailto:{{ user.email }}"
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm no-underline transition-colors duration-200"
|
||||
style="background-color: rgba(22,118,123,0.08); color: #16767b;">
|
||||
<i class="fas fa-envelope" style="font-size: 0.85em; opacity: 0.7;"></i>
|
||||
{{ user.email }}
|
||||
</a>
|
||||
{% if user.phone %}
|
||||
<a href="tel:{{ user.phone }}"
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm no-underline transition-colors duration-200"
|
||||
style="background-color: rgba(22,118,123,0.08); color: #16767b;">
|
||||
<i class="fas fa-phone" style="font-size: 0.85em; opacity: 0.7;"></i>
|
||||
{{ user.phone }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="text-sm text-gray-900">{{ user.company or 'No company' }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex flex-row flex-wrap gap-1.5">
|
||||
{% if user.email != current_user.email and not user.is_admin %}
|
||||
<form method="POST" action="{{ url_for('contacts.toggle_active', id=user.id) }}" class="inline">
|
||||
<button type="submit"
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm font-medium transition-colors duration-200 cursor-pointer"
|
||||
style="background-color: {% if user.is_active %}rgba(34,197,94,0.1){% else %}rgba(239,68,68,0.1){% endif %}; color: {% if user.is_active %}#15803d{% else %}#b91c1c{% endif %};">
|
||||
<i class="fas fa-{% if user.is_active %}check-circle{% else %}times-circle{% endif %}" style="font-size: 0.85em; opacity: 0.7;"></i>
|
||||
{{ 'Active' if user.is_active else 'Inactive' }}
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm font-medium"
|
||||
style="background-color: {% if user.is_active %}rgba(34,197,94,0.1){% else %}rgba(239,68,68,0.1){% endif %}; color: {% if user.is_active %}#15803d{% else %}#b91c1c{% endif %};">
|
||||
<i class="fas fa-{% if user.is_active %}check-circle{% else %}times-circle{% endif %}" style="font-size: 0.85em; opacity: 0.7;"></i>
|
||||
{{ 'Active' if user.is_active else 'Inactive' }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex flex-row flex-wrap gap-1.5">
|
||||
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm font-medium"
|
||||
style="background-color: {% if user.is_admin %}rgba(147,51,234,0.1){% else %}rgba(107,114,128,0.1){% endif %}; color: {% if user.is_admin %}#7e22ce{% else %}#374151{% endif %};">
|
||||
<i class="fas fa-{% if user.is_admin %}shield-alt{% else %}user{% endif %}" style="font-size: 0.85em; opacity: 0.7;"></i>
|
||||
{{ 'Admin' if user.is_admin else 'User' }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<div class="flex justify-end gap-1.5">
|
||||
<a href="{{ url_for('contacts.edit_contact', id=user.id) }}"
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm no-underline transition-colors duration-200"
|
||||
style="background-color: rgba(22,118,123,0.08); color: #16767b;">
|
||||
<i class="fas fa-edit" style="font-size: 0.85em; opacity: 0.7;"></i>
|
||||
Edit
|
||||
</a>
|
||||
{% if user.email != current_user.email %}
|
||||
<form method="POST" action="{{ url_for('contacts.delete_contact', id=user.id) }}" class="inline"
|
||||
onsubmit="return confirm('Are you sure you want to delete this contact?');">
|
||||
<button type="submit"
|
||||
class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-sm no-underline transition-colors duration-200"
|
||||
style="background-color: rgba(239,68,68,0.1); color: #b91c1c;">
|
||||
<i class="fas fa-trash" style="font-size: 0.85em; opacity: 0.7;"></i>
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if pagination and pagination.pages > 1 %}
|
||||
<div class="mt-6 flex justify-center">
|
||||
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
|
||||
{% if pagination.has_prev %}
|
||||
<a href="{{ url_for('contacts.contacts_list', page=pagination.prev_num, **request.args) }}"
|
||||
class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
<span class="sr-only">Previous</span>
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% for page in pagination.iter_pages() %}
|
||||
{% if page %}
|
||||
<a href="{{ url_for('contacts.contacts_list', page=page, **request.args) }}"
|
||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium {% if page == pagination.page %}text-[#16767b] bg-[#16767b]/10{% else %}text-gray-700 hover:bg-gray-50{% endif %}">
|
||||
{{ page }}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700">
|
||||
...
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if pagination.has_next %}
|
||||
<a href="{{ url_for('contacts.contacts_list', page=pagination.next_num, **request.args) }}"
|
||||
class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
|
||||
<span class="sr-only">Next</span>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user