Update Sprint & Task Management style

This commit is contained in:
2025-07-09 10:43:25 +02:00
committed by Jens Luedicke
parent bd681b5c00
commit 032472a621
6 changed files with 159 additions and 196 deletions

View File

@@ -6,7 +6,8 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash,
from models import (db, Company, User, Role, Team, Project, TimeEntry, SystemSettings, from models import (db, Company, User, Role, Team, Project, TimeEntry, SystemSettings,
SystemEvent, BrandingSettings, Task, SubTask, TaskDependency, Sprint, SystemEvent, BrandingSettings, Task, SubTask, TaskDependency, Sprint,
Comment, UserPreferences, UserDashboard, WorkConfig, CompanySettings, Comment, UserPreferences, UserDashboard, WorkConfig, CompanySettings,
CompanyWorkConfig, ProjectCategory) CompanyWorkConfig, ProjectCategory, Note, NoteFolder, NoteShare,
Announcement, CompanyInvitation)
from routes.auth import system_admin_required from routes.auth import system_admin_required
from flask import session from flask import session
from sqlalchemy import func from sqlalchemy import func
@@ -226,6 +227,34 @@ def delete_company(company_id):
db.session.query(User.id).filter(User.company_id == company_id) db.session.query(User.id).filter(User.company_id == company_id)
)).delete(synchronize_session=False) )).delete(synchronize_session=False)
# Delete notes and note-related data
user_ids_subquery = db.session.query(User.id).filter(User.company_id == company_id).subquery()
# Delete note shares
NoteShare.query.filter(NoteShare.created_by_id.in_(user_ids_subquery)).delete(synchronize_session=False)
# Delete notes
Note.query.filter(Note.created_by_id.in_(user_ids_subquery)).delete(synchronize_session=False)
# Delete note folders
NoteFolder.query.filter(NoteFolder.created_by_id.in_(user_ids_subquery)).delete(synchronize_session=False)
# Delete announcements
Announcement.query.filter(Announcement.created_by_id.in_(user_ids_subquery)).delete(synchronize_session=False)
# Delete invitations
CompanyInvitation.query.filter(
(CompanyInvitation.invited_by_id.in_(user_ids_subquery)) |
(CompanyInvitation.accepted_by_user_id.in_(user_ids_subquery))
).delete(synchronize_session=False)
# Delete system events associated with users from this company
SystemEvent.query.filter(SystemEvent.user_id.in_(user_ids_subquery)).delete(synchronize_session=False)
# Clear branding settings updated_by references
BrandingSettings.query.filter(BrandingSettings.updated_by_id.in_(user_ids_subquery)).update(
{BrandingSettings.updated_by_id: None}, synchronize_session=False)
# Delete users # Delete users
User.query.filter_by(company_id=company_id).delete() User.query.filter_by(company_id=company_id).delete()

View File

@@ -530,8 +530,8 @@ button {
/* Button Outline Variants */ /* Button Outline Variants */
.btn-outline { .btn-outline {
border: 1px solid #007bff; border: 1px solid #6c757d;
color: #007bff; color: #495057;
background: transparent; background: transparent;
} }

View File

@@ -65,7 +65,7 @@
<div class="category-card" data-category-id="{{ category.id }}"> <div class="category-card" data-category-id="{{ category.id }}">
<div class="category-header" style="background: linear-gradient(135deg, {{ category.color }}20 0%, {{ category.color }}10 100%); border-left: 4px solid {{ category.color }};"> <div class="category-header" style="background: linear-gradient(135deg, {{ category.color }}20 0%, {{ category.color }}10 100%); border-left: 4px solid {{ category.color }};">
<div class="category-title"> <div class="category-title">
<span class="category-icon">{{ category.icon or '<i class="ti ti-folder"></i>' }}</span> <span class="category-icon">{{ category.icon|safe if category.icon else '<i class="ti ti-folder"></i>'|safe }}</span>
<span class="category-name">{{ category.name }}</span> <span class="category-name">{{ category.name }}</span>
</div> </div>
<div class="category-stats"> <div class="category-stats">
@@ -141,7 +141,7 @@
{% if project.category %} {% if project.category %}
<div class="project-category"> <div class="project-category">
<span class="category-badge" style="background-color: {{ project.category.color }}20; color: {{ project.category.color }};"> <span class="category-badge" style="background-color: {{ project.category.color }}20; color: {{ project.category.color }};">
{{ project.category.icon or '<i class="ti ti-folder"></i>' }} {{ project.category.name }} {{ project.category.icon|safe if project.category.icon else '<i class="ti ti-folder"></i>'|safe }} {{ project.category.name }}
</span> </span>
</div> </div>
{% endif %} {% endif %}
@@ -226,7 +226,7 @@
<td> <td>
{% if project.category %} {% if project.category %}
<span class="category-badge" style="background-color: {{ project.category.color }}20; color: {{ project.category.color }};"> <span class="category-badge" style="background-color: {{ project.category.color }}20; color: {{ project.category.color }};">
{{ project.category.icon or '<i class="ti ti-folder"></i>' }} {{ project.category.name }} {{ project.category.icon|safe if project.category.icon else '<i class="ti ti-folder"></i>'|safe }} {{ project.category.name }}
</span> </span>
{% else %} {% else %}
<span class="text-muted">-</span> <span class="text-muted">-</span>

View File

@@ -2,8 +2,16 @@
{% block content %} {% block content %}
<div class="page-container timetrack-container"> <div class="page-container timetrack-container">
<div class="page-header analytics-header"> <div class="page-header">
<h2><i class="ti ti-chart-bar"></i> Time Analytics</h2> <div class="header-content">
<div class="header-left">
<h1 class="page-title">
<span class="page-icon"><i class="ti ti-chart-bar"></i></span>
Time Analytics
</h1>
<p class="page-subtitle">Analyze time tracking data and generate insights</p>
</div>
<div class="header-actions">
<div class="mode-switcher"> <div class="mode-switcher">
<button class="mode-btn {% if mode == 'personal' %}active{% endif %}" <button class="mode-btn {% if mode == 'personal' %}active{% endif %}"
onclick="switchMode('personal')">Personal</button> onclick="switchMode('personal')">Personal</button>
@@ -13,6 +21,8 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
</div>
<!-- Unified Filter Panel --> <!-- Unified Filter Panel -->
<div class="filter-panel"> <div class="filter-panel">

View File

@@ -1,43 +1,56 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="management-container sprint-management-container"> <div class="page-container">
<!-- Header Section --> <!-- Header Section -->
<div class="management-header sprint-header"> <div class="page-header">
<h1>🏃‍♂️ Sprint Management</h1> <div class="header-content">
<div class="management-controls sprint-controls"> <div class="header-left">
<!-- View Switcher --> <h1 class="page-title">
<span class="page-icon"><i class="ti ti-run"></i></span>
Sprint Management
</h1>
<p class="page-subtitle">Manage sprints and track progress</p>
</div>
<div class="header-actions">
<button id="refresh-sprints" class="btn btn-secondary">
<i class="ti ti-refresh"></i>
Refresh
</button>
<button id="add-sprint-btn" class="btn btn-primary">
<i class="ti ti-plus"></i>
New Sprint
</button>
</div>
</div>
</div>
<!-- Filter Section -->
<div class="filter-section">
<div class="view-switcher"> <div class="view-switcher">
<button class="view-btn active" data-view="active">Active Sprints</button> <button class="view-btn active" data-view="active">Active Sprints</button>
<button class="view-btn" data-view="all">All Sprints</button> <button class="view-btn" data-view="all">All Sprints</button>
<button class="view-btn" data-view="planning">Planning</button> <button class="view-btn" data-view="planning">Planning</button>
<button class="view-btn" data-view="completed">Completed</button> <button class="view-btn" data-view="completed">Completed</button>
</div> </div>
<!-- Actions -->
<div class="management-actions sprint-actions">
<button id="add-sprint-btn" class="btn btn-primary">+ New Sprint</button>
<button id="refresh-sprints" class="btn btn-secondary">🔄 Refresh</button>
</div>
</div>
</div> </div>
<!-- Sprint Statistics --> <!-- Sprint Statistics -->
<div class="management-stats sprint-stats"> <div class="stats-section">
<div class="stat-card"> <div class="stat-card">
<div class="stat-number" id="total-sprints">0</div> <div class="stat-value" id="total-sprints">0</div>
<div class="stat-label">Total Sprints</div> <div class="stat-label">Total Sprints</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-number" id="active-sprints">0</div> <div class="stat-value" id="active-sprints">0</div>
<div class="stat-label">Active</div> <div class="stat-label">Active</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-number" id="completed-sprints">0</div> <div class="stat-value" id="completed-sprints">0</div>
<div class="stat-label">Completed</div> <div class="stat-label">Completed</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-number" id="total-tasks">0</div> <div class="stat-value" id="total-tasks">0</div>
<div class="stat-label">Total Tasks</div> <div class="stat-label">Total Tasks</div>
</div> </div>
</div> </div>
@@ -112,7 +125,7 @@
<div class="hybrid-date-input"> <div class="hybrid-date-input">
<input type="date" id="sprint-start-date-native" class="date-input-native" required> <input type="date" id="sprint-start-date-native" class="date-input-native" required>
<input type="text" id="sprint-start-date" class="date-input-formatted" required placeholder="{{ "YYYY-MM-DD" if (g.user.preferences.date_format if g.user.preferences else "ISO") == "ISO" else "MM/DD/YYYY" if (g.user.preferences.date_format if g.user.preferences else "ISO") == "US" else "DD/MM/YYYY" if (g.user.preferences.date_format if g.user.preferences else "ISO") in ["EU", "UK"] else "Mon, Dec 25, 2024" }}"> <input type="text" id="sprint-start-date" class="date-input-formatted" required placeholder="{{ "YYYY-MM-DD" if (g.user.preferences.date_format if g.user.preferences else "ISO") == "ISO" else "MM/DD/YYYY" if (g.user.preferences.date_format if g.user.preferences else "ISO") == "US" else "DD/MM/YYYY" if (g.user.preferences.date_format if g.user.preferences else "ISO") in ["EU", "UK"] else "Mon, Dec 25, 2024" }}">
<button type="button" class="calendar-picker-btn" onclick="openCalendarPicker('sprint-start-date')" title="Open calendar">📅</button> <button type="button" class="calendar-picker-btn" onclick="openCalendarPicker('sprint-start-date')" title="Open calendar"><i class="ti ti-calendar"></i></button>
</div> </div>
<div class="date-error" id="sprint-start-date-error" style="display: none; color: #dc3545; font-size: 0.8rem; margin-top: 0.25rem;"></div> <div class="date-error" id="sprint-start-date-error" style="display: none; color: #dc3545; font-size: 0.8rem; margin-top: 0.25rem;"></div>
</div> </div>
@@ -121,7 +134,7 @@
<div class="hybrid-date-input"> <div class="hybrid-date-input">
<input type="date" id="sprint-end-date-native" class="date-input-native" required> <input type="date" id="sprint-end-date-native" class="date-input-native" required>
<input type="text" id="sprint-end-date" class="date-input-formatted" required placeholder="{{ "YYYY-MM-DD" if (g.user.preferences.date_format if g.user.preferences else "ISO") == "ISO" else "MM/DD/YYYY" if (g.user.preferences.date_format if g.user.preferences else "ISO") == "US" else "DD/MM/YYYY" if (g.user.preferences.date_format if g.user.preferences else "ISO") in ["EU", "UK"] else "Mon, Dec 25, 2024" }}"> <input type="text" id="sprint-end-date" class="date-input-formatted" required placeholder="{{ "YYYY-MM-DD" if (g.user.preferences.date_format if g.user.preferences else "ISO") == "ISO" else "MM/DD/YYYY" if (g.user.preferences.date_format if g.user.preferences else "ISO") == "US" else "DD/MM/YYYY" if (g.user.preferences.date_format if g.user.preferences else "ISO") in ["EU", "UK"] else "Mon, Dec 25, 2024" }}">
<button type="button" class="calendar-picker-btn" onclick="openCalendarPicker('sprint-end-date')" title="Open calendar">📅</button> <button type="button" class="calendar-picker-btn" onclick="openCalendarPicker('sprint-end-date')" title="Open calendar"><i class="ti ti-calendar"></i></button>
</div> </div>
<div class="date-error" id="sprint-end-date-error" style="display: none; color: #dc3545; font-size: 0.8rem; margin-top: 0.25rem;"></div> <div class="date-error" id="sprint-end-date-error" style="display: none; color: #dc3545; font-size: 0.8rem; margin-top: 0.25rem;"></div>
</div> </div>
@@ -143,38 +156,23 @@
<!-- Styles --> <!-- Styles -->
<style> <style>
.sprint-management-container { /* Container styles - using default page spacing */
padding: 1rem;
max-width: 100%;
margin: 0 auto;
}
.sprint-header { /* Header styles handled by common page-header classes */
display: flex;
justify-content: space-between; .filter-section {
align-items: center;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
flex-wrap: wrap; padding: 1rem;
gap: 1rem; background: #f8f9fa;
} border-radius: 8px;
.sprint-header h1 {
margin: 0;
color: #333;
}
.sprint-controls {
display: flex;
gap: 1rem;
flex-wrap: wrap;
align-items: center;
} }
.view-switcher { .view-switcher {
display: flex; display: flex;
background: #f8f9fa; background: white;
border-radius: 6px; border-radius: 6px;
padding: 2px; padding: 2px;
width: fit-content;
} }
.view-btn { .view-btn {
@@ -198,37 +196,7 @@
color: #212529; color: #212529;
} }
.sprint-actions { /* Statistics styles handled by common stats-section classes */
display: flex;
gap: 0.5rem;
}
.sprint-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin-bottom: 1.5rem;
}
.stat-card {
background: white;
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
text-align: center;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: #007bff;
}
.stat-label {
font-size: 0.9rem;
color: #666;
margin-top: 0.25rem;
}
.sprint-grid { .sprint-grid {
display: grid; display: grid;
@@ -731,7 +699,7 @@ class SprintManager {
</div> </div>
<div class="sprint-dates"> <div class="sprint-dates">
📅 ${formatUserDate(sprint.start_date)} - ${formatUserDate(sprint.end_date)} <i class="ti ti-calendar"></i> ${formatUserDate(sprint.start_date)} - ${formatUserDate(sprint.end_date)}
${sprint.days_remaining > 0 ? `(${sprint.days_remaining} days left)` : ''} ${sprint.days_remaining > 0 ? `(${sprint.days_remaining} days left)` : ''}
</div> </div>

View File

@@ -1,12 +1,36 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="management-container task-management-container"> <div class="page-container">
<!-- Header Section --> <!-- Header Section -->
<div class="management-header task-header"> <div class="page-header">
<h1><i class="ti ti-clipboard-list"></i> Task Management</h1> <div class="header-content">
<div class="management-controls task-controls"> <div class="header-left">
<!-- Smart Search --> <h1 class="page-title">
<span class="page-icon"><i class="ti ti-clipboard-list"></i></span>
Task Management
</h1>
<p class="page-subtitle">Manage and track all tasks across projects</p>
</div>
<div class="header-actions">
<button id="refresh-tasks" class="btn btn-secondary">
<i class="ti ti-refresh"></i>
Refresh
</button>
<button id="toggle-archived" class="btn btn-outline" title="Show/Hide Archived Tasks">
<i class="ti ti-archive"></i>
Show Archived
</button>
<button id="add-task-btn" class="btn btn-primary">
<i class="ti ti-plus"></i>
Add Task
</button>
</div>
</div>
</div>
<!-- Search Section -->
<div class="search-section">
<div class="smart-search-container"> <div class="smart-search-container">
<div class="smart-search-box"> <div class="smart-search-box">
<input type="text" id="smart-search-input" class="smart-search-input" placeholder="Search tasks... (e.g., my-tasks priority:high, project:TimeTrack, overdue)"> <input type="text" id="smart-search-input" class="smart-search-input" placeholder="Search tasks... (e.g., my-tasks priority:high, project:TimeTrack, overdue)">
@@ -16,36 +40,28 @@
<!-- Suggestions will be populated here --> <!-- Suggestions will be populated here -->
</div> </div>
</div> </div>
<!-- Actions -->
<div class="management-actions task-actions">
<button id="add-task-btn" class="btn btn-primary"><i class="ti ti-plus"></i> Add Task</button>
<button id="refresh-tasks" class="btn btn-secondary"><i class="ti ti-refresh"></i> Refresh</button>
<button id="toggle-archived" class="btn btn-outline" title="Show/Hide Archived Tasks"><i class="ti ti-archive"></i> Show Archived</button>
</div>
</div>
</div> </div>
<!-- Task Statistics --> <!-- Task Statistics -->
<div class="management-stats task-stats"> <div class="stats-section">
<div class="stat-card"> <div class="stat-card">
<div class="stat-number" id="total-tasks">0</div> <div class="stat-value" id="total-tasks">0</div>
<div class="stat-label">Total Tasks</div> <div class="stat-label">Total Tasks</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-number" id="completed-tasks">0</div> <div class="stat-value" id="completed-tasks">0</div>
<div class="stat-label">Completed</div> <div class="stat-label">Completed</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-number" id="in-progress-tasks">0</div> <div class="stat-value" id="in-progress-tasks">0</div>
<div class="stat-label">In Progress</div> <div class="stat-label">In Progress</div>
</div> </div>
<div class="stat-card"> <div class="stat-card">
<div class="stat-number" id="overdue-tasks">0</div> <div class="stat-value" id="overdue-tasks">0</div>
<div class="stat-label">Overdue</div> <div class="stat-label">Overdue</div>
</div> </div>
<div class="stat-card" id="archived-stat-card" style="display: none;"> <div class="stat-card" id="archived-stat-card" style="display: none;">
<div class="stat-number" id="archived-tasks">0</div> <div class="stat-value" id="archived-tasks">0</div>
<div class="stat-label">Archived</div> <div class="stat-label">Archived</div>
</div> </div>
</div> </div>
@@ -129,12 +145,26 @@
<!-- Styles --> <!-- Styles -->
<style> <style>
/* Header adjustments for Task Management */
.header-actions {
display: flex;
gap: 0.75rem;
align-items: center;
flex-wrap: wrap;
}
/* Search Section */
.search-section {
margin-bottom: 1.5rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 8px;
}
/* Smart Search Styles */ /* Smart Search Styles */
.smart-search-container { .smart-search-container {
margin-bottom: 1rem;
position: relative; position: relative;
width: 100%; width: 100%;
flex: 1;
} }
.smart-search-box { .smart-search-box {
@@ -237,71 +267,9 @@
font-size: 0.8rem; font-size: 0.8rem;
} }
/* Task Management Layout */ /* Task Management specific styles removed - using common page styles */
.task-controls {
display: flex;
gap: 1rem;
align-items: center;
flex-wrap: wrap;
width: 100%;
}
.task-controls .smart-search-container { /* Responsive adjustments handled by common page styles */
flex: 1;
min-width: 300px;
max-width: 600px;
margin-bottom: 0; /* Remove margin to align with buttons */
}
.task-controls .management-actions {
flex-shrink: 0;
display: flex;
gap: 0.5rem;
align-items: center;
}
/* Ensure all buttons and search input have same height */
.smart-search-input,
.task-controls .btn {
height: 38px; /* Standard height for consistency */
}
.task-controls .btn {
padding: 0.5rem 1rem;
display: inline-flex;
align-items: center;
justify-content: center;
white-space: nowrap; /* Prevent button text from wrapping */
}
/* Responsive adjustments */
@media (max-width: 992px) {
.task-controls {
flex-direction: column;
align-items: stretch;
}
.task-controls .smart-search-container {
max-width: 100%;
margin-bottom: 0.5rem;
}
.task-controls .management-actions {
justify-content: center;
}
}
@media (max-width: 576px) {
.task-controls .management-actions {
flex-wrap: wrap;
gap: 0.25rem;
}
.task-controls .btn {
font-size: 0.875rem;
padding: 0.4rem 0.8rem;
}
}
/* Subtask progress styles */ /* Subtask progress styles */
.task-subtasks { .task-subtasks {
@@ -336,18 +304,6 @@
white-space: nowrap; white-space: nowrap;
} }
@media (max-width: 768px) {
.task-controls {
flex-direction: column;
align-items: stretch;
gap: 0.75rem;
}
.task-controls .smart-search-container {
min-width: auto;
max-width: none;
}
}