Marwan Alwali 0a037d3d9d update
2025-09-01 11:26:11 +03:00

1279 lines
40 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}{{ project.name }} - Quality Project{% endblock %}
{% block extra_css %}
<link href="{% static 'assets/plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
<style>
.project-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 0.5rem;
padding: 2rem;
margin-bottom: 2rem;
}
.project-layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.project-main {
display: flex;
flex-direction: column;
gap: 2rem;
}
.project-sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.section-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 0.5rem;
overflow: hidden;
}
.section-header {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.5rem;
font-weight: 600;
color: #495057;
display: flex;
justify-content: between;
align-items: center;
}
.section-content {
padding: 1.5rem;
}
.project-status {
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
}
.status-planning { background: #e3f2fd; color: #1976d2; }
.status-active { background: #e8f5e8; color: #2e7d32; }
.status-on-hold { background: #fff3e0; color: #f57c00; }
.status-completed { background: #f3e5f5; color: #7b1fa2; }
.status-cancelled { background: #ffebee; color: #d32f2f; }
.priority-badge {
padding: 0.375rem 0.75rem;
border-radius: 0.375rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.priority-low { background: #e8f5e8; color: #2e7d32; }
.priority-medium { background: #fff3e0; color: #f57c00; }
.priority-high { background: #ffebee; color: #d32f2f; }
.priority-critical { background: #f3e5f5; color: #7b1fa2; }
.progress-container {
margin-bottom: 1.5rem;
}
.progress-header {
display: flex;
justify-content: between;
align-items: center;
margin-bottom: 0.5rem;
}
.progress-label {
font-weight: 600;
color: #495057;
}
.progress-percentage {
font-size: 1.25rem;
font-weight: bold;
color: #007bff;
}
.progress-bar-container {
background: #e9ecef;
border-radius: 0.5rem;
height: 12px;
overflow: hidden;
position: relative;
}
.progress-bar {
background: linear-gradient(90deg, #007bff, #0056b3);
height: 100%;
transition: width 0.3s ease;
border-radius: 0.5rem;
}
.progress-milestones {
display: flex;
justify-content: between;
margin-top: 0.5rem;
}
.milestone {
font-size: 0.75rem;
color: #6c757d;
text-align: center;
flex: 1;
}
.milestone.completed {
color: #28a745;
font-weight: 600;
}
.milestone.active {
color: #007bff;
font-weight: 600;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.info-item {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.info-label {
font-size: 0.75rem;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
}
.info-value {
font-size: 0.875rem;
color: #495057;
font-weight: 500;
}
.team-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.team-member {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
transition: all 0.2s;
}
.team-member:hover {
border-color: #007bff;
background: #f8f9fa;
}
.member-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #007bff;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.875rem;
}
.member-info {
flex: 1;
}
.member-name {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.member-role {
font-size: 0.75rem;
color: #6c757d;
}
.timeline-container {
position: relative;
}
.timeline-item {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
position: relative;
}
.timeline-item:not(:last-child)::after {
content: '';
position: absolute;
left: 15px;
top: 40px;
bottom: -24px;
width: 2px;
background: #dee2e6;
}
.timeline-icon {
width: 32px;
height: 32px;
border-radius: 50%;
background: #007bff;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
flex-shrink: 0;
z-index: 1;
position: relative;
}
.timeline-icon.completed {
background: #28a745;
}
.timeline-icon.pending {
background: #6c757d;
}
.timeline-content {
flex: 1;
padding-top: 0.25rem;
}
.timeline-title {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.timeline-description {
font-size: 0.875rem;
color: #6c757d;
margin-bottom: 0.5rem;
}
.timeline-meta {
display: flex;
gap: 1rem;
font-size: 0.75rem;
color: #6c757d;
}
.deliverables-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
}
.deliverable-card {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
transition: all 0.2s;
}
.deliverable-card:hover {
border-color: #007bff;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.deliverable-header {
display: flex;
justify-content: between;
align-items: start;
margin-bottom: 0.75rem;
}
.deliverable-title {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.deliverable-type {
font-size: 0.75rem;
color: #6c757d;
}
.deliverable-status {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.deliverable-description {
font-size: 0.875rem;
color: #6c757d;
margin-bottom: 0.75rem;
}
.deliverable-meta {
display: flex;
justify-content: between;
align-items: center;
font-size: 0.75rem;
color: #6c757d;
}
.risk-item {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
padding: 1rem;
margin-bottom: 1rem;
transition: all 0.2s;
}
.risk-item:hover {
border-color: #007bff;
}
.risk-header {
display: flex;
justify-content: between;
align-items: start;
margin-bottom: 0.75rem;
}
.risk-title {
font-weight: 600;
color: #495057;
}
.risk-level {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.risk-low { background: #e8f5e8; color: #2e7d32; }
.risk-medium { background: #fff3e0; color: #f57c00; }
.risk-high { background: #ffebee; color: #d32f2f; }
.risk-description {
font-size: 0.875rem;
color: #6c757d;
margin-bottom: 0.75rem;
}
.risk-mitigation {
background: #f8f9fa;
border-radius: 0.25rem;
padding: 0.75rem;
font-size: 0.875rem;
color: #495057;
}
.activity-item {
display: flex;
gap: 0.75rem;
padding: 0.75rem 0;
border-bottom: 1px solid #f0f0f0;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: #007bff;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 600;
flex-shrink: 0;
}
.activity-content {
flex: 1;
}
.activity-text {
font-size: 0.875rem;
color: #495057;
margin-bottom: 0.25rem;
}
.activity-meta {
font-size: 0.75rem;
color: #6c757d;
}
.document-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
margin-bottom: 0.75rem;
transition: all 0.2s;
}
.document-item:hover {
border-color: #007bff;
background: #f8f9fa;
}
.document-icon {
width: 40px;
height: 40px;
border-radius: 0.375rem;
background: #007bff;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
}
.document-info {
flex: 1;
}
.document-name {
font-weight: 600;
color: #495057;
margin-bottom: 0.25rem;
}
.document-meta {
font-size: 0.75rem;
color: #6c757d;
}
.action-buttons {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.btn-action {
padding: 0.5rem 1rem;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
background: white;
color: #495057;
text-decoration: none;
transition: all 0.2s;
font-size: 0.875rem;
cursor: pointer;
}
.btn-action:hover {
border-color: #007bff;
color: #007bff;
text-decoration: none;
}
.btn-primary-action {
background: #007bff;
border-color: #007bff;
color: white;
}
.btn-primary-action:hover {
background: #0056b3;
border-color: #0056b3;
color: white;
}
.stats-mini {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
.stat-mini {
text-align: center;
padding: 1rem;
background: #f8f9fa;
border-radius: 0.375rem;
}
.stat-mini-number {
font-size: 1.5rem;
font-weight: bold;
color: #007bff;
margin-bottom: 0.25rem;
}
.stat-mini-label {
font-size: 0.75rem;
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
}
@media (max-width: 1200px) {
.project-layout {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.project-sidebar {
order: -1;
}
}
@media (max-width: 768px) {
.project-header {
padding: 1.5rem;
}
.info-grid {
grid-template-columns: 1fr;
}
.team-grid {
grid-template-columns: 1fr;
}
.deliverables-grid {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
}
.stats-mini {
grid-template-columns: 1fr;
}
}
@media print {
.project-sidebar, .action-buttons {
display: none !important;
}
.project-layout {
grid-template-columns: 1fr;
}
.section-header {
background: none;
border-bottom: 2px solid #000;
color: #000;
}
}
</style>
{% endblock %}
{% block content %}
<div id="content" class="app-content">
<!-- Page Header -->
<div class="d-flex align-items-center mb-3">
<div>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'quality:dashboard' %}">Quality</a></li>
<li class="breadcrumb-item"><a href="{% url 'quality:project_list' %}">Projects</a></li>
<li class="breadcrumb-item active">{{ project.name }}</li>
</ol>
<h1 class="page-header mb-0">
<i class="fas fa-project-diagram me-2"></i>Project Details
</h1>
</div>
<div class="ms-auto">
<div class="action-buttons">
<button type="button" class="btn-action" onclick="exportProject()">
<i class="fas fa-download me-1"></i>Export
</button>
<button type="button" class="btn-action" onclick="generateReport()">
<i class="fas fa-file-alt me-1"></i>Report
</button>
{% if project.can_edit %}
<a href="{% url 'quality:project_edit' project.id %}" class="btn-action">
<i class="fas fa-edit me-1"></i>Edit
</a>
{% endif %}
{% if project.status == 'planning' %}
<button type="button" class="btn-primary-action" onclick="startProject()">
<i class="fas fa-play me-1"></i>Start Project
</button>
{% elif project.status == 'active' %}
<button type="button" class="btn-primary-action" onclick="completeProject()">
<i class="fas fa-check me-1"></i>Complete Project
</button>
{% endif %}
</div>
</div>
</div>
<!-- Project Header -->
<div class="project-header">
<div class="row align-items-center">
<div class="col-md-8">
<h2 class="mb-2">{{ project.name }}</h2>
<p class="mb-3">{{ project.description }}</p>
<div class="d-flex align-items-center gap-3 flex-wrap">
<span class="project-status status-{{ project.status }}">
{{ project.get_status_display }}
</span>
<span class="priority-badge priority-{{ project.priority|lower }}">
{{ project.get_priority_display }} Priority
</span>
<span class="badge bg-light text-dark">
<i class="fas fa-hashtag me-1"></i>{{ project.project_id|default:project.id }}
</span>
</div>
</div>
<div class="col-md-4 text-md-end">
<div class="stats-mini">
<div class="stat-mini">
<div class="stat-mini-number">{{ project.progress|default:0 }}%</div>
<div class="stat-mini-label">Complete</div>
</div>
<div class="stat-mini">
<div class="stat-mini-number">{{ project.team_members.count|default:0 }}</div>
<div class="stat-mini-label">Team Members</div>
</div>
</div>
</div>
</div>
</div>
<div class="project-layout">
<!-- Main Content -->
<div class="project-main">
<!-- Progress Section -->
{% if project.progress is not None %}
<div class="section-card">
<div class="section-header">
<i class="fas fa-chart-line me-2"></i>Project Progress
</div>
<div class="section-content">
<div class="progress-container">
<div class="progress-header">
<span class="progress-label">Overall Progress</span>
<span class="progress-percentage">{{ project.progress }}%</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar" style="width: {{ project.progress }}%;"></div>
</div>
<div class="progress-milestones">
<div class="milestone {% if project.progress >= 25 %}completed{% elif project.progress >= 0 %}active{% endif %}">
Planning
</div>
<div class="milestone {% if project.progress >= 50 %}completed{% elif project.progress >= 25 %}active{% endif %}">
Execution
</div>
<div class="milestone {% if project.progress >= 75 %}completed{% elif project.progress >= 50 %}active{% endif %}">
Review
</div>
<div class="milestone {% if project.progress >= 100 %}completed{% elif project.progress >= 75 %}active{% endif %}">
Completion
</div>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Project Information -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-info-circle me-2"></i>Project Information
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Project Manager</div>
<div class="info-value">{{ project.manager.get_full_name|default:"Not assigned" }}</div>
</div>
<div class="info-item">
<div class="info-label">Department</div>
<div class="info-value">{{ project.department.name|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Start Date</div>
<div class="info-value">{{ project.start_date|date:"M d, Y"|default:"Not set" }}</div>
</div>
<div class="info-item">
<div class="info-label">End Date</div>
<div class="info-value">{{ project.end_date|date:"M d, Y"|default:"Not set" }}</div>
</div>
<div class="info-item">
<div class="info-label">Budget</div>
<div class="info-value">${{ project.budget|floatformat:2|default:"Not specified" }}</div>
</div>
<div class="info-item">
<div class="info-label">Created</div>
<div class="info-value">{{ project.created_at|date:"M d, Y g:i A" }}</div>
</div>
</div>
</div>
</div>
<!-- Project Timeline -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-timeline me-2"></i>Project Timeline
</div>
<div class="section-content">
<div class="timeline-container">
{% for milestone in project.milestones.all %}
<div class="timeline-item">
<div class="timeline-icon {% if milestone.is_completed %}completed{% elif milestone.is_current %}active{% else %}pending{% endif %}">
<i class="fas fa-{{ milestone.icon|default:'circle' }}"></i>
</div>
<div class="timeline-content">
<div class="timeline-title">{{ milestone.name }}</div>
<div class="timeline-description">{{ milestone.description }}</div>
<div class="timeline-meta">
<span><i class="fas fa-calendar me-1"></i>{{ milestone.due_date|date:"M d, Y" }}</span>
{% if milestone.assignee %}
<span><i class="fas fa-user me-1"></i>{{ milestone.assignee.get_full_name }}</span>
{% endif %}
</div>
</div>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fas fa-timeline fa-2x mb-2"></i>
<p>No milestones defined yet</p>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Deliverables -->
<div class="section-card">
<div class="section-header">
<div>
<i class="fas fa-box me-2"></i>Deliverables ({{ project.deliverables.count }})
</div>
{% if project.can_edit %}
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addDeliverable()">
<i class="fas fa-plus me-1"></i>Add
</button>
{% endif %}
</div>
<div class="section-content">
<div class="deliverables-grid">
{% for deliverable in project.deliverables.all %}
<div class="deliverable-card">
<div class="deliverable-header">
<div>
<div class="deliverable-title">{{ deliverable.name }}</div>
<div class="deliverable-type">{{ deliverable.get_type_display }}</div>
</div>
<span class="deliverable-status status-{{ deliverable.status }}">
{{ deliverable.get_status_display }}
</span>
</div>
<div class="deliverable-description">
{{ deliverable.description|truncatechars:100 }}
</div>
<div class="deliverable-meta">
<span>Due: {{ deliverable.due_date|date:"M d, Y" }}</span>
{% if deliverable.assignee %}
<span>{{ deliverable.assignee.get_full_name }}</span>
{% endif %}
</div>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fas fa-box fa-2x mb-2"></i>
<p>No deliverables defined yet</p>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Project Risks -->
<div class="section-card">
<div class="section-header">
<div>
<i class="fas fa-exclamation-triangle me-2"></i>Risks & Issues ({{ project.risks.count }})
</div>
{% if project.can_edit %}
<button type="button" class="btn btn-outline-warning btn-sm" onclick="addRisk()">
<i class="fas fa-plus me-1"></i>Add Risk
</button>
{% endif %}
</div>
<div class="section-content">
{% for risk in project.risks.all %}
<div class="risk-item">
<div class="risk-header">
<div class="risk-title">{{ risk.title }}</div>
<span class="risk-level risk-{{ risk.level|lower }}">
{{ risk.get_level_display }}
</span>
</div>
<div class="risk-description">
{{ risk.description }}
</div>
{% if risk.mitigation %}
<div class="risk-mitigation">
<strong>Mitigation:</strong> {{ risk.mitigation }}
</div>
{% endif %}
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fas fa-shield-alt fa-2x mb-2"></i>
<p>No risks identified</p>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Sidebar -->
<div class="project-sidebar">
<!-- Team Members -->
<div class="section-card">
<div class="section-header">
<div>
<i class="fas fa-users me-2"></i>Team ({{ project.team_members.count }})
</div>
{% if project.can_edit %}
<button type="button" class="btn btn-outline-primary btn-sm" onclick="manageTeam()">
<i class="fas fa-user-plus"></i>
</button>
{% endif %}
</div>
<div class="section-content">
<div class="team-grid">
{% for member in project.team_members.all %}
<div class="team-member">
<div class="member-avatar">
{{ member.first_name.0|upper }}{{ member.last_name.0|upper }}
</div>
<div class="member-info">
<div class="member-name">{{ member.get_full_name }}</div>
<div class="member-role">{{ member.profile.role|default:"Team Member" }}</div>
</div>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fas fa-users fa-2x mb-2"></i>
<p>No team members assigned</p>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Recent Activity -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-history me-2"></i>Recent Activity
</div>
<div class="section-content">
{% for activity in project.activities.all|slice:":10" %}
<div class="activity-item">
<div class="activity-avatar">
{{ activity.user.first_name.0|upper }}{{ activity.user.last_name.0|upper }}
</div>
<div class="activity-content">
<div class="activity-text">{{ activity.description }}</div>
<div class="activity-meta">
{{ activity.created_at|timesince }} ago
</div>
</div>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fas fa-history fa-2x mb-2"></i>
<p>No recent activity</p>
</div>
{% endfor %}
</div>
</div>
<!-- Project Documents -->
<div class="section-card">
<div class="section-header">
<div>
<i class="fas fa-folder me-2"></i>Documents ({{ project.documents.count }})
</div>
{% if project.can_edit %}
<button type="button" class="btn btn-outline-primary btn-sm" onclick="uploadDocument()">
<i class="fas fa-upload"></i>
</button>
{% endif %}
</div>
<div class="section-content">
{% for document in project.documents.all %}
<div class="document-item">
<div class="document-icon">
<i class="fas fa-file-{{ document.get_icon }}"></i>
</div>
<div class="document-info">
<div class="document-name">{{ document.name }}</div>
<div class="document-meta">
{{ document.size|filesizeformat }} • {{ document.uploaded_at|date:"M d, Y" }}
</div>
</div>
<a href="{{ document.file.url }}" class="btn btn-outline-primary btn-sm" target="_blank">
<i class="fas fa-download"></i>
</a>
</div>
{% empty %}
<div class="text-center text-muted py-3">
<i class="fas fa-folder-open fa-2x mb-2"></i>
<p>No documents uploaded</p>
</div>
{% endfor %}
</div>
</div>
<!-- Project Statistics -->
<div class="section-card">
<div class="section-header">
<i class="fas fa-chart-bar me-2"></i>Statistics
</div>
<div class="section-content">
<div class="info-grid">
<div class="info-item">
<div class="info-label">Days Active</div>
<div class="info-value">{{ project.days_active|default:0 }}</div>
</div>
<div class="info-item">
<div class="info-label">Tasks Completed</div>
<div class="info-value">{{ project.completed_tasks|default:0 }}</div>
</div>
<div class="info-item">
<div class="info-label">Budget Used</div>
<div class="info-value">{{ project.budget_used_percentage|default:0 }}%</div>
</div>
<div class="info-item">
<div class="info-label">Quality Score</div>
<div class="info-value">{{ project.quality_score|default:"N/A" }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Team Management Modal -->
<div class="modal fade" id="teamModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-users me-2"></i>Manage Team Members
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Add Team Members</label>
<select class="form-select" id="team-members" multiple>
{% for user in available_users %}
<option value="{{ user.id }}">{{ user.get_full_name }} ({{ user.email }})</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label">Current Team Members</label>
<div id="current-team">
{% for member in project.team_members.all %}
<div class="d-flex justify-content-between align-items-center p-2 border rounded mb-2">
<span>{{ member.get_full_name }}</span>
<button type="button" class="btn btn-outline-danger btn-sm" onclick="removeMember({{ member.id }})">
<i class="fas fa-times"></i>
</button>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Cancel
</button>
<button type="button" class="btn btn-primary" onclick="saveTeamChanges()">
<i class="fas fa-save me-1"></i>Save Changes
</button>
</div>
</div>
</div>
</div>
<!-- Document Upload Modal -->
<div class="modal fade" id="documentModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-upload me-2"></i>Upload Document
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="document-form" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">Document Name</label>
<input type="text" class="form-control" id="document-name" required>
</div>
<div class="mb-3">
<label class="form-label">Document Type</label>
<select class="form-select" id="document-type">
<option value="report">Report</option>
<option value="plan">Plan</option>
<option value="specification">Specification</option>
<option value="other">Other</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">File</label>
<input type="file" class="form-control" id="document-file" required>
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea class="form-control" id="document-description" rows="3"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>Cancel
</button>
<button type="button" class="btn btn-primary" onclick="uploadDocumentFile()">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'assets/plugins/select2/dist/js/select2.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize Select2
$('#team-members').select2({
placeholder: 'Select team members...',
width: '100%'
});
});
function startProject() {
if (confirm('Are you sure you want to start this project?')) {
fetch(`/quality/projects/{{ project.id }}/start/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Project started successfully', 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error starting project', 'danger');
}
})
.catch(error => {
showAlert('Error starting project', 'danger');
});
}
}
function completeProject() {
if (confirm('Are you sure you want to mark this project as completed?')) {
fetch(`/quality/projects/{{ project.id }}/complete/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Project completed successfully', 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error completing project', 'danger');
}
})
.catch(error => {
showAlert('Error completing project', 'danger');
});
}
}
function generateReport() {
window.open(`/quality/projects/{{ project.id }}/report/`, '_blank');
}
function exportProject() {
window.open(`/quality/projects/{{ project.id }}/export/`, '_blank');
}
function manageTeam() {
new bootstrap.Modal(document.getElementById('teamModal')).show();
}
function removeMember(memberId) {
if (confirm('Remove this team member from the project?')) {
fetch(`/quality/projects/{{ project.id }}/remove-member/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json'
},
body: JSON.stringify({ member_id: memberId })
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Team member removed', 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error removing team member', 'danger');
}
})
.catch(error => {
showAlert('Error removing team member', 'danger');
});
}
}
function saveTeamChanges() {
const selectedMembers = $('#team-members').val();
fetch(`/quality/projects/{{ project.id }}/update-team/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json'
},
body: JSON.stringify({ member_ids: selectedMembers })
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Team updated successfully', 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error updating team', 'danger');
}
})
.catch(error => {
showAlert('Error updating team', 'danger');
});
bootstrap.Modal.getInstance(document.getElementById('teamModal')).hide();
}
function uploadDocument() {
new bootstrap.Modal(document.getElementById('documentModal')).show();
}
function uploadDocumentFile() {
const formData = new FormData();
formData.append('name', document.getElementById('document-name').value);
formData.append('type', document.getElementById('document-type').value);
formData.append('description', document.getElementById('document-description').value);
formData.append('file', document.getElementById('document-file').files[0]);
fetch(`/quality/projects/{{ project.id }}/upload-document/`, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('Document uploaded successfully', 'success');
setTimeout(() => location.reload(), 1500);
} else {
showAlert('Error uploading document', 'danger');
}
})
.catch(error => {
showAlert('Error uploading document', 'danger');
});
bootstrap.Modal.getInstance(document.getElementById('documentModal')).hide();
}
function addDeliverable() {
window.location.href = `/quality/projects/{{ project.id }}/deliverables/add/`;
}
function addRisk() {
window.location.href = `/quality/projects/{{ project.id }}/risks/add/`;
}
function showAlert(message, type) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 1060; min-width: 300px;';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alertDiv);
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
</script>
{% endblock %}