feat: Add user filter and flexible column layout to Kanban board

- Add 'Assigned To' filter dropdown to filter tasks by assigned user
  - Enables users to view their personal todo landscape
  - Filter works in combination with existing project filter
  - Shows all active users sorted by display name

- Replace fixed grid layout with flexible flexbox layout
  - Columns now expand evenly to fill available horizontal space
  - Added min-width constraint to prevent columns from becoming too narrow
  - Maintains horizontal scrolling on smaller screens for better mobile UX

This improves the Kanban board usability by allowing users to focus on
their assigned tasks and provides a better visual layout that adapts
to the number of columns and screen size.
This commit is contained in:
Dries Peeters
2025-12-12 22:24:19 +01:00
parent bde61c7f5d
commit a83aabb006
3 changed files with 17 additions and 5 deletions
+8 -3
View File
@@ -12,11 +12,14 @@ kanban_bp = Blueprint("kanban", __name__)
@kanban_bp.route("/kanban")
@login_required
def board():
"""Kanban board page with optional project filter"""
"""Kanban board page with optional project and user filters"""
project_id = request.args.get("project_id", type=int)
user_id = request.args.get("user_id", type=int)
query = Task.query
if project_id:
query = query.filter_by(project_id=project_id)
if user_id:
query = query.filter_by(assigned_to=user_id)
# Order tasks for stable rendering
tasks = query.order_by(Task.priority.desc(), Task.due_date.asc(), Task.created_at.asc()).all()
# Fresh columns - use project-specific columns if project_id is provided
@@ -34,12 +37,14 @@ def board():
else:
columns = []
# Provide projects for filter dropdown
from app.models import Project
from app.models import Project, User
projects = Project.query.filter_by(status="active").order_by(Project.name).all()
# Provide users for filter dropdown (active users only)
users = User.query.filter_by(is_active=True).order_by(User.full_name, User.username).all()
# No-cache
response = render_template(
"kanban/board.html", tasks=tasks, kanban_columns=columns, projects=projects, project_id=project_id
"kanban/board.html", tasks=tasks, kanban_columns=columns, projects=projects, users=users, project_id=project_id, user_id=user_id
)
resp = make_response(response)
resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate, max-age=0"
+7
View File
@@ -18,6 +18,13 @@
<option value="{{ p.id }}" {% if project_id == p.id %}selected{% endif %}>{{ p.name }}</option>
{% endfor %}
</select>
<label for="user_id" class="text-sm text-text-muted-light dark:text-text-muted-dark ml-2">{{ _('Assigned To') }}</label>
<select id="user_id" name="user_id" class="bg-background-light dark:bg-gray-700 border border-transparent dark:border-transparent rounded-lg py-2 px-3 text-sm text-text-light dark:text-text-dark" onchange="this.form.submit()">
<option value="">{{ _('All') }}</option>
{% for u in users %}
<option value="{{ u.id }}" {% if user_id == u.id %}selected{% endif %}>{{ u.display_name }}</option>
{% endfor %}
</select>
</form>
{% if current_user.is_admin %}
<a href="{{ url_for('kanban.list_columns') }}" class="inline-flex items-center gap-2 bg-primary text-white text-sm px-3 py-2 rounded hover:bg-primary/90 transition-colors">
+2 -2
View File
@@ -1,9 +1,9 @@
{# Reusable Kanban board for tasks. Expects `tasks` and `kanban_columns` in context. #}
<div class="kanban-board-wrapper p-4">
<div id="kanbanBoard" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-6" role="list" aria-label="{{ _('Kanban board columns') }}">
<div id="kanbanBoard" class="flex flex-row gap-6 overflow-x-auto" role="list" aria-label="{{ _('Kanban board columns') }}">
{% for col in kanban_columns %}
<div class="bg-card-light dark:bg-card-dark rounded-xl shadow-sm ring-1 ring-border-light/60 dark:ring-border-dark/60 flex flex-col" role="region" aria-labelledby="col-{{ col.key }}-title">
<div class="bg-card-light dark:bg-card-dark rounded-xl shadow-sm ring-1 ring-border-light/60 dark:ring-border-dark/60 flex flex-col flex-1 min-w-[280px] max-w-full" role="region" aria-labelledby="col-{{ col.key }}-title">
<div class="p-4 border-b border-border-light dark:border-border-dark flex justify-between items-center bg-gradient-to-b from-background-light/60 dark:from-background-dark/40 to-transparent rounded-t-xl">
<h3 id="col-{{ col.key }}-title" class="text-base font-semibold flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full" style="background-color: {{ col.color or '#4A90E2' }}"></span>