Add complete project management system with role-based access control: **Core Features:** - Project creation and management for Admins/Supervisors - Time tracking with optional project selection and notes - Project-based filtering and reporting in history - Enhanced export functionality with project data - Team-specific project assignments **Database Changes:** - New Project model with full relationships - Enhanced TimeEntry model with project_id and notes - Updated migration scripts with rollback support - Sample project creation for testing **User Interface:** - Project management templates (create, edit, list) - Enhanced time tracking with project dropdown - Project filtering in history page - Updated navigation for role-based access - Modern styling with hover effects and responsive design **API Enhancements:** - Project validation and access control - Updated arrive endpoint with project support - Enhanced export functions with project data - Role-based route protection **Migration Support:** - Comprehensive migration scripts (migrate_projects.py) - Updated main migration script (migrate_db.py) - Detailed migration documentation - Rollback functionality for safe deployment **Role-Based Access:** - Admins: Full project CRUD operations - Supervisors: Project creation and management - Team Leaders: View team hours with projects - Team Members: Select projects when tracking time 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
127 lines
3.7 KiB
HTML
127 lines
3.7 KiB
HTML
{% extends "layout.html" %}
|
|
|
|
{% block content %}
|
|
<div class="timetrack-container">
|
|
<div class="admin-header">
|
|
<h2>Project Management</h2>
|
|
<a href="{{ url_for('create_project') }}" class="btn">Create New Project</a>
|
|
</div>
|
|
|
|
{% if projects %}
|
|
<div class="projects-table">
|
|
<table class="time-history">
|
|
<thead>
|
|
<tr>
|
|
<th>Code</th>
|
|
<th>Name</th>
|
|
<th>Team</th>
|
|
<th>Status</th>
|
|
<th>Start Date</th>
|
|
<th>End Date</th>
|
|
<th>Created By</th>
|
|
<th>Time Entries</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for project in projects %}
|
|
<tr class="{% if not project.is_active %}inactive-project{% endif %}">
|
|
<td><strong>{{ project.code }}</strong></td>
|
|
<td>{{ project.name }}</td>
|
|
<td>
|
|
{% if project.team %}
|
|
{{ project.team.name }}
|
|
{% else %}
|
|
<em>All Teams</em>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="status-badge {% if project.is_active %}active{% else %}inactive{% endif %}">
|
|
{{ 'Active' if project.is_active else 'Inactive' }}
|
|
</span>
|
|
</td>
|
|
<td>{{ project.start_date.strftime('%Y-%m-%d') if project.start_date else '-' }}</td>
|
|
<td>{{ project.end_date.strftime('%Y-%m-%d') if project.end_date else '-' }}</td>
|
|
<td>{{ project.created_by.username }}</td>
|
|
<td>{{ project.time_entries|length }}</td>
|
|
<td class="actions">
|
|
<a href="{{ url_for('edit_project', project_id=project.id) }}" class="btn btn-small">Edit</a>
|
|
{% if g.user.role.name == 'ADMIN' and project.time_entries|length == 0 %}
|
|
<form method="POST" action="{{ url_for('delete_project', project_id=project.id) }}" style="display: inline;"
|
|
onsubmit="return confirm('Are you sure you want to delete this project?')">
|
|
<button type="submit" class="btn btn-small btn-danger">Delete</button>
|
|
</form>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="no-data">
|
|
<p>No projects found. <a href="{{ url_for('create_project') }}">Create your first project</a>.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<style>
|
|
.admin-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.projects-table {
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.inactive-project {
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.status-badge {
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-badge.active {
|
|
background-color: #d4edda;
|
|
color: #155724;
|
|
}
|
|
|
|
.status-badge.inactive {
|
|
background-color: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
|
|
.actions {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.btn-small {
|
|
padding: 0.375rem 0.75rem;
|
|
font-size: 0.875rem;
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
.btn-danger {
|
|
background-color: #dc3545;
|
|
border-color: #dc3545;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background-color: #c82333;
|
|
border-color: #bd2130;
|
|
}
|
|
|
|
.no-data {
|
|
text-align: center;
|
|
padding: 3rem;
|
|
color: #666;
|
|
}
|
|
</style>
|
|
{% endblock %} |