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,
SystemEvent, BrandingSettings, Task, SubTask, TaskDependency, Sprint,
Comment, UserPreferences, UserDashboard, WorkConfig, CompanySettings,
CompanyWorkConfig, ProjectCategory)
CompanyWorkConfig, ProjectCategory, Note, NoteFolder, NoteShare,
Announcement, CompanyInvitation)
from routes.auth import system_admin_required
from flask import session
from sqlalchemy import func
@@ -226,6 +227,34 @@ def delete_company(company_id):
db.session.query(User.id).filter(User.company_id == company_id)
)).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
User.query.filter_by(company_id=company_id).delete()

View File

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

View File

@@ -65,7 +65,7 @@
<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-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>
</div>
<div class="category-stats">
@@ -141,7 +141,7 @@
{% if project.category %}
<div class="project-category">
<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>
</div>
{% endif %}
@@ -226,7 +226,7 @@
<td>
{% if project.category %}
<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>
{% else %}
<span class="text-muted">-</span>

View File

@@ -2,15 +2,25 @@
{% block content %}
<div class="page-container timetrack-container">
<div class="page-header analytics-header">
<h2><i class="ti ti-chart-bar"></i> Time Analytics</h2>
<div class="mode-switcher">
<button class="mode-btn {% if mode == 'personal' %}active{% endif %}"
onclick="switchMode('personal')">Personal</button>
{% if g.user.team_id and g.user.role in [Role.TEAM_LEADER, Role.SUPERVISOR, Role.ADMIN] %}
<button class="mode-btn {% if mode == 'team' %}active{% endif %}"
onclick="switchMode('team')">Team</button>
{% endif %}
<div class="page-header">
<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">
<button class="mode-btn {% if mode == 'personal' %}active{% endif %}"
onclick="switchMode('personal')">Personal</button>
{% if g.user.team_id and g.user.role in [Role.TEAM_LEADER, Role.SUPERVISOR, Role.ADMIN] %}
<button class="mode-btn {% if mode == 'team' %}active{% endif %}"
onclick="switchMode('team')">Team</button>
{% endif %}
</div>
</div>
</div>
</div>

View File

@@ -1,43 +1,56 @@
{% extends "layout.html" %}
{% block content %}
<div class="management-container sprint-management-container">
<div class="page-container">
<!-- Header Section -->
<div class="management-header sprint-header">
<h1>🏃‍♂️ Sprint Management</h1>
<div class="management-controls sprint-controls">
<!-- View Switcher -->
<div class="view-switcher">
<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="planning">Planning</button>
<button class="view-btn" data-view="completed">Completed</button>
<div class="page-header">
<div class="header-content">
<div class="header-left">
<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>
<!-- 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 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">
<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="planning">Planning</button>
<button class="view-btn" data-view="completed">Completed</button>
</div>
</div>
<!-- Sprint Statistics -->
<div class="management-stats sprint-stats">
<div class="stats-section">
<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>
<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>
<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>
<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>
</div>
@@ -112,7 +125,7 @@
<div class="hybrid-date-input">
<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" }}">
<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 class="date-error" id="sprint-start-date-error" style="display: none; color: #dc3545; font-size: 0.8rem; margin-top: 0.25rem;"></div>
</div>
@@ -121,7 +134,7 @@
<div class="hybrid-date-input">
<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" }}">
<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 class="date-error" id="sprint-end-date-error" style="display: none; color: #dc3545; font-size: 0.8rem; margin-top: 0.25rem;"></div>
</div>
@@ -143,38 +156,23 @@
<!-- Styles -->
<style>
.sprint-management-container {
padding: 1rem;
max-width: 100%;
margin: 0 auto;
}
/* Container styles - using default page spacing */
.sprint-header {
display: flex;
justify-content: space-between;
align-items: center;
/* Header styles handled by common page-header classes */
.filter-section {
margin-bottom: 1.5rem;
flex-wrap: wrap;
gap: 1rem;
}
.sprint-header h1 {
margin: 0;
color: #333;
}
.sprint-controls {
display: flex;
gap: 1rem;
flex-wrap: wrap;
align-items: center;
padding: 1rem;
background: #f8f9fa;
border-radius: 8px;
}
.view-switcher {
display: flex;
background: #f8f9fa;
background: white;
border-radius: 6px;
padding: 2px;
width: fit-content;
}
.view-btn {
@@ -198,37 +196,7 @@
color: #212529;
}
.sprint-actions {
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;
}
/* Statistics styles handled by common stats-section classes */
.sprint-grid {
display: grid;
@@ -731,7 +699,7 @@ class SprintManager {
</div>
<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)` : ''}
</div>

View File

@@ -1,51 +1,67 @@
{% extends "layout.html" %}
{% block content %}
<div class="management-container task-management-container">
<div class="page-container">
<!-- Header Section -->
<div class="management-header task-header">
<h1><i class="ti ti-clipboard-list"></i> Task Management</h1>
<div class="management-controls task-controls">
<!-- Smart Search -->
<div class="smart-search-container">
<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)">
<button type="button" class="smart-search-clear" id="smart-search-clear" title="Clear search"><i class="ti ti-x"></i></button>
</div>
<div class="smart-search-suggestions" id="smart-search-suggestions" style="display: none;">
<!-- Suggestions will be populated here -->
</div>
<div class="page-header">
<div class="header-content">
<div class="header-left">
<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>
<!-- 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 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-box">
<input type="text" id="smart-search-input" class="smart-search-input" placeholder="Search tasks... (e.g., my-tasks priority:high, project:TimeTrack, overdue)">
<button type="button" class="smart-search-clear" id="smart-search-clear" title="Clear search"><i class="ti ti-x"></i></button>
</div>
<div class="smart-search-suggestions" id="smart-search-suggestions" style="display: none;">
<!-- Suggestions will be populated here -->
</div>
</div>
</div>
<!-- Task Statistics -->
<div class="management-stats task-stats">
<div class="stats-section">
<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>
<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>
<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>
<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>
<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>
</div>
@@ -129,12 +145,26 @@
<!-- Styles -->
<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-container {
margin-bottom: 1rem;
position: relative;
width: 100%;
flex: 1;
}
.smart-search-box {
@@ -237,71 +267,9 @@
font-size: 0.8rem;
}
/* Task Management Layout */
.task-controls {
display: flex;
gap: 1rem;
align-items: center;
flex-wrap: wrap;
width: 100%;
}
/* Task Management specific styles removed - using common page styles */
.task-controls .smart-search-container {
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;
}
}
/* Responsive adjustments handled by common page styles */
/* Subtask progress styles */
.task-subtasks {
@@ -336,18 +304,6 @@
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;
}
}