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:
|
if admin is None:
|
||||||
return HTMLResponse("Forbidden", status_code=403)
|
return HTMLResponse("Forbidden", status_code=403)
|
||||||
|
|
||||||
# Placeholder — will be implemented in Task 4
|
per_page = 20
|
||||||
return HTMLResponse("Admin users list")
|
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