diff --git a/app.py b/app.py index b8300a7..a6f0864 100644 --- a/app.py +++ b/app.py @@ -3725,10 +3725,18 @@ def unified_task_management(): User.role.in_([Role.ADMIN, Role.SUPERVISOR]) ).order_by(User.username).all() + # Convert team members to JSON-serializable format + team_members_data = [{ + 'id': member.id, + 'username': member.username, + 'email': member.email, + 'role': member.role.value if member.role else 'Team Member' + } for member in team_members] + return render_template('unified_task_management.html', title='Task Management', available_projects=available_projects, - team_members=team_members) + team_members=team_members_data) # Sprint Management Route @app.route('/sprints') @@ -3815,7 +3823,14 @@ def create_task(): db.session.add(task) db.session.commit() - return jsonify({'success': True, 'message': 'Task created successfully'}) + return jsonify({ + 'success': True, + 'message': 'Task created successfully', + 'task': { + 'id': task.id, + 'task_number': task.task_number + } + }) except Exception as e: db.session.rollback() @@ -3836,17 +3851,33 @@ def get_task(task_id): task_data = { 'id': task.id, + 'task_number': getattr(task, 'task_number', f'TSK-{task.id:03d}'), 'name': task.name, 'description': task.description, 'status': task.status.name, 'priority': task.priority.name, 'estimated_hours': task.estimated_hours, 'assigned_to_id': task.assigned_to_id, + 'assigned_to_name': task.assigned_to.username if task.assigned_to else None, + 'project_id': task.project_id, + 'project_name': task.project.name if task.project else None, + 'project_code': task.project.code if task.project else None, 'start_date': task.start_date.isoformat() if task.start_date else None, - 'due_date': task.due_date.isoformat() if task.due_date else None + 'due_date': task.due_date.isoformat() if task.due_date else None, + 'completed_date': task.completed_date.isoformat() if task.completed_date else None, + 'archived_date': task.archived_date.isoformat() if task.archived_date else None, + 'sprint_id': task.sprint_id, + 'subtasks': [{ + 'id': subtask.id, + 'name': subtask.name, + 'status': subtask.status.name, + 'priority': subtask.priority.name, + 'assigned_to_id': subtask.assigned_to_id, + 'assigned_to_name': subtask.assigned_to.username if subtask.assigned_to else None + } for subtask in task.subtasks] if task.subtasks else [] } - return jsonify({'success': True, 'task': task_data}) + return jsonify(task_data) except Exception as e: return jsonify({'success': False, 'message': str(e)}) @@ -3977,6 +4008,14 @@ def get_unified_tasks(): 'created_at': task.created_at.isoformat(), 'is_team_task': is_team_task, 'subtask_count': len(task.subtasks) if task.subtasks else 0, + 'subtasks': [{ + 'id': subtask.id, + 'name': subtask.name, + 'status': subtask.status.name, + 'priority': subtask.priority.name, + 'assigned_to_id': subtask.assigned_to_id, + 'assigned_to_name': subtask.assigned_to.username if subtask.assigned_to else None + } for subtask in task.subtasks] if task.subtasks else [], 'sprint_id': task.sprint_id, 'sprint_name': task.sprint.name if task.sprint else None, 'is_current_sprint': task.sprint.is_current if task.sprint else False diff --git a/migrate_db.py b/migrate_db.py index 1b53d4c..276a842 100644 --- a/migrate_db.py +++ b/migrate_db.py @@ -684,11 +684,21 @@ def migrate_task_system(db_path): created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_by_id INTEGER NOT NULL, - FOREIGN KEY (task_id) REFERENCES task (id), + FOREIGN KEY (task_id) REFERENCES task (id) ON DELETE CASCADE, FOREIGN KEY (assigned_to_id) REFERENCES user (id), FOREIGN KEY (created_by_id) REFERENCES user (id) ) """) + + # Create index for better performance + print("Creating index on sub_task.task_id...") + cursor.execute("CREATE INDEX idx_subtask_task_id ON sub_task(task_id)") + else: + # Check if the index exists + cursor.execute("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_subtask_task_id'") + if not cursor.fetchone(): + print("Creating missing index on sub_task.task_id...") + cursor.execute("CREATE INDEX idx_subtask_task_id ON sub_task(task_id)") # Check if task_dependency table exists cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='task_dependency'") @@ -1073,6 +1083,41 @@ def migrate_postgresql_schema(): """)) db.session.commit() + # Check if sub_task table exists + result = db.session.execute(text(""" + SELECT table_name + FROM information_schema.tables + WHERE table_name = 'sub_task' + """)) + + if not result.fetchone(): + print("Creating sub_task table...") + db.session.execute(text(""" + CREATE TABLE sub_task ( + id SERIAL PRIMARY KEY, + name VARCHAR(200) NOT NULL, + description TEXT, + status taskstatus DEFAULT 'NOT_STARTED', + priority taskpriority DEFAULT 'MEDIUM', + estimated_hours FLOAT, + task_id INTEGER NOT NULL, + assigned_to_id INTEGER, + start_date DATE, + due_date DATE, + completed_date DATE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by_id INTEGER NOT NULL, + FOREIGN KEY (task_id) REFERENCES task (id) ON DELETE CASCADE, + FOREIGN KEY (assigned_to_id) REFERENCES "user" (id), + FOREIGN KEY (created_by_id) REFERENCES "user" (id) + ) + """)) + + # Create index for better performance + db.session.execute(text("CREATE INDEX idx_subtask_task_id ON sub_task(task_id)")) + db.session.commit() + print("PostgreSQL schema migration completed successfully!") except Exception as e: diff --git a/static/js/subtasks.js b/static/js/subtasks.js new file mode 100644 index 0000000..b41ff69 --- /dev/null +++ b/static/js/subtasks.js @@ -0,0 +1,363 @@ +// Sub-task Management Functions + +// Global variable to track subtasks +let currentSubtasks = []; + +// Initialize subtasks when loading a task +function initializeSubtasks(taskId) { + currentSubtasks = []; + const subtasksContainer = document.getElementById('subtasks-container'); + if (!subtasksContainer) return; + + subtasksContainer.innerHTML = '
No subtasks yet
'; + } + }) + .catch(error => { + console.error('Error loading subtasks:', error); + subtasksContainer.innerHTML = 'Error loading subtasks
'; + }); + } else { + renderSubtasks(); + } +} + +// Render subtasks in the modal +function renderSubtasks() { + const container = document.getElementById('subtasks-container'); + if (!container) return; + + if (currentSubtasks.length === 0) { + container.innerHTML = 'No subtasks yet
'; + return; + } + + container.innerHTML = currentSubtasks.map((subtask, index) => ` +