feat: add admin user list page with search and pagination
This commit is contained in:
parent
f2d669d705
commit
1a795914f9
4 changed files with 111 additions and 2 deletions
|
|
@ -30,5 +30,31 @@ async def users_list(request: Request) -> Response:
|
|||
if admin is None:
|
||||
return HTMLResponse("Forbidden", status_code=403)
|
||||
|
||||
# Placeholder — will be implemented in Task 4
|
||||
return HTMLResponse("Admin users list")
|
||||
per_page = 20
|
||||
q = request.query_params.get("q", "")
|
||||
offset = int(request.query_params.get("offset", "0"))
|
||||
|
||||
user_repo = request.app.state.user_repo
|
||||
if q:
|
||||
users = await user_repo.search_users(q, offset, per_page)
|
||||
total = await user_repo.count_users(query=q)
|
||||
else:
|
||||
users = await user_repo.list_users(offset, per_page)
|
||||
total = await user_repo.count_users()
|
||||
|
||||
context = {
|
||||
"users": users,
|
||||
"query": q,
|
||||
"offset": offset,
|
||||
"per_page": per_page,
|
||||
"total": total,
|
||||
"active_page": "users",
|
||||
}
|
||||
|
||||
# HTMX search requests get just the table rows partial
|
||||
if request.headers.get("HX-Request") and request.headers.get("HX-Trigger-Name") == "q":
|
||||
templates = request.app.state.templates
|
||||
return templates.TemplateResponse(request, "admin/_user_rows.html", context)
|
||||
|
||||
templates = request.app.state.templates
|
||||
return templates.TemplateResponse(request, "admin/users.html", context)
|
||||
|
|
|
|||
13
src/porchlight/templates/admin/_pagination.html
Normal file
13
src/porchlight/templates/admin/_pagination.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{% if total > 0 %}
|
||||
<span>
|
||||
Showing {{ offset + 1 }}–{{ offset + users|length }} of {{ total }}
|
||||
</span>
|
||||
{% endif %}
|
||||
<span>
|
||||
{% if offset > 0 %}
|
||||
<a href="/admin/users?offset={{ offset - per_page }}&q={{ query or '' }}">Previous</a>
|
||||
{% endif %}
|
||||
{% if offset + per_page < total %}
|
||||
<a href="/admin/users?offset={{ offset + per_page }}&q={{ query or '' }}">Next</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
21
src/porchlight/templates/admin/_user_rows.html
Normal file
21
src/porchlight/templates/admin/_user_rows.html
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{% for user in users %}
|
||||
<tr>
|
||||
<td><a href="/admin/users/{{ user.userid }}">{{ user.username }}</a></td>
|
||||
<td>{{ [user.given_name, user.family_name]|select|join(' ') }}</td>
|
||||
<td>{{ user.email or '' }}</td>
|
||||
<td>{% for g in user.groups %}<span class="group-tag">{{ g }}</span> {% endfor %}</td>
|
||||
<td>
|
||||
<span id="status-{{ user.userid }}">
|
||||
{% if user.active %}
|
||||
<span class="status-badge status-active">Active</span>
|
||||
{% else %}
|
||||
<span class="status-badge status-inactive">Inactive</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ user.created_at.strftime('%Y-%m-%d') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not users %}
|
||||
<tr><td colspan="6">No users found.</td></tr>
|
||||
{% endif %}
|
||||
49
src/porchlight/templates/admin/users.html
Normal file
49
src/porchlight/templates/admin/users.html
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block title %}Users — Admin — Porchlight{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
<h1>Users</h1>
|
||||
|
||||
<section>
|
||||
<h2>Create invite</h2>
|
||||
<form hx-post="/admin/invite" hx-target="#invite-status" hx-swap="innerHTML">
|
||||
<div class="admin-search">
|
||||
<input type="text" name="username" placeholder="Username for new invite" required>
|
||||
<button type="submit">Create invite</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="invite-status"></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="admin-search">
|
||||
<input type="search" name="q" placeholder="Search by username or email..."
|
||||
hx-get="/admin/users" hx-target="#user-table-body" hx-swap="innerHTML"
|
||||
hx-trigger="input changed delay:300ms, search"
|
||||
hx-include="this" hx-push-url="false"
|
||||
value="{{ query or '' }}">
|
||||
</div>
|
||||
|
||||
<div id="user-table-container">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Groups</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="user-table-body">
|
||||
{% include "admin/_user_rows.html" %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination" id="pagination">
|
||||
{% include "admin/_pagination.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue