mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-04-25 22:19:09 -05:00
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:
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user