320 lines
13 KiB
HTML
320 lines
13 KiB
HTML
{% extends 'mobile/mobile_base.html' %}
|
|
|
|
{% load static %}
|
|
{% load i18n %}
|
|
{% load document_viewer_tags %}
|
|
|
|
{% block title %}
|
|
{{ project.name }}
|
|
{% endblock title %}
|
|
|
|
{% block upleft %}
|
|
<a href="javascript:history.back()" class="nav-link" style="color: white;">
|
|
<i class="bi bi-arrow-left fs-3" title="Retour"></i>
|
|
</a>
|
|
{% endblock upleft %}
|
|
|
|
{% block content %}
|
|
|
|
<!-- En-tête du projet -->
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<h5 class="card-title text-primary">{{ project.name }}</h5>
|
|
<p class="card-text text-muted">{{ project.code }}</p>
|
|
|
|
<p class="mb-1"><strong>{% translate "Statut" %} :</strong>
|
|
<span class="badge bg-info">{{ project.get_status_display }}</span>
|
|
</p>
|
|
|
|
<p class="mb-1"><strong>{% translate "Type de maintenance" %} :</strong>
|
|
<span class="badge bg-secondary">{{ project.get_maintain_type_display }}</span>
|
|
</p>
|
|
|
|
<p class="mb-1"><strong>{% translate "Thématique" %} :</strong>
|
|
{% if project.thematic %}
|
|
{{ project.thematic.name }}
|
|
{% endif%}
|
|
</p>
|
|
|
|
<p class="mb-1"><strong>{% translate "Dates" %} :</strong>
|
|
{{ project.start_date|date:"d/m/Y" }} -
|
|
{% if project.end_date %}
|
|
{{ project.end_date|date:"d/m/Y" }}
|
|
{% else %}
|
|
En cours
|
|
{% endif%}
|
|
</p>
|
|
|
|
{% if project.description %}
|
|
<p class="mt-3"><strong>{% translate "Description" %} :</strong> {{ project.description }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Phases du projet -->
|
|
<div class="mt-4">
|
|
<h5 class="text-primary">{% translate "Phases du projet" %}</h5>
|
|
{% if project.phases.all %}
|
|
<div class="list-group">
|
|
{% for phase in project.phases.all %}
|
|
<div class="list-group-item">
|
|
<h6 class="mb-1">{{ phase.name }}</h6>
|
|
<p class="small text-muted">
|
|
{{ phase.start_date|date:"d/m/Y" }} -
|
|
{% if phase.end_date %}
|
|
{{ phase.end_date|date:"d/m/Y" }}
|
|
{% else %}
|
|
{% translate "En cours" %}
|
|
{% endif%}
|
|
</p>
|
|
{% if phase.description %}
|
|
<p class="mb-0 small">{{ phase.description }}</p>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<p class="text-muted">{% translate "Aucune phase définie pour ce projet." %}</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<h5 class="text-primary">{% translate "Contrôles associés" %}</h5>
|
|
<a href="{% url 'controls:controls_add' %}?project_id={{ project.id }}&v=mobile" class="btn btn-light w-100 mt-3 mb-3">
|
|
<i class="bi bi-plus-circle px-3 text-primary"></i>{% translate "Ajouter un contrôle" %}
|
|
</a>
|
|
{% if project.controls.all %}
|
|
<div class="list-group">
|
|
{% for control in project.controls.all %}
|
|
<div class="list-group-item">
|
|
<h6 class="mb-1">
|
|
<a href="{% url 'mobile:controls_detail' control.id %}" >
|
|
{{ control.control_code }}
|
|
</a>
|
|
</h6>
|
|
<p class="small text-muted">
|
|
{% translate "Type" %} : <span class="badge bg-secondary">{{ control.get_control_type_display }}</span>
|
|
| {% translate "Statut" %} : <span class="badge bg-info">{{ control.get_status_display }}</span>
|
|
</p>
|
|
<p class="mb-0 small">{% translate "Lieu" %} : {{ control.location }}</p>
|
|
<p class="mb-0 text-muted small">{% translate "Mis à jour le" %} : {{ control.updated_at|date:"d/m/Y H:i" }}</p>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<p class="text-muted">{% translate "Aucun contrôle associé à ce projet." %}</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Documents -->
|
|
<div class="mt-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<h5 class="text-primary mb-0">{% translate "Documents" %}</h5>
|
|
{% if can_add_documents %}
|
|
<button class="btn btn-light border-0" title="{% translate 'Ajouter' %}" data-bs-toggle="modal" data-bs-target="#addDocumentModal">
|
|
<i class="bi bi-plus-circle text-primary fs-5"></i>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div id="documentsAlertContainer" class="m-1"></div>
|
|
|
|
{% if use_managed_documents %}
|
|
{% render_document_viewer project_documents modal_id="projectDocViewerModal" show_tags=True show_versions=True location_id=project.id %}
|
|
{% else %}
|
|
<p class="text-muted small">{% translate "Aucun document disponible." %}</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if can_add_documents %}
|
|
<!-- Modal ajout de documents -->
|
|
<div class="modal fade" id="addDocumentModal" tabindex="-1" aria-labelledby="addDocumentModalLabel" aria-hidden="true" style="z-index: 10050;">
|
|
<div class="modal-dialog modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="addDocumentModalLabel">{% translate "Ajouter des documents" %}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="addDocumentForm" method="post" enctype="multipart/form-data" action="{% url 'projects:projects_add_document' project.id %}">
|
|
{% csrf_token %}
|
|
<div class="mb-3">
|
|
<label for="docFileInput" class="form-label">{% translate "Sélectionner des fichiers" %}</label>
|
|
<input type="file" id="docFileInput" name="files" multiple class="form-control">
|
|
</div>
|
|
<div id="docFileFieldsContainer">
|
|
{# Champs dynamiques (tag) injectés par JS #}
|
|
</div>
|
|
<div class="d-flex justify-content-end mt-3">
|
|
<button type="submit" class="btn btn-primary" id="documentSubmitButton">
|
|
{% translate "Ajouter" %}
|
|
<span class="spinner-border spinner-border-sm ms-2 d-none" role="status" aria-hidden="true" id="documentSubmitSpinner"></span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Modal historique des versions -->
|
|
<div class="modal fade" id="documentHistoryModal" tabindex="-1" aria-labelledby="documentHistoryModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-scrollable modal-xl">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="documentHistoryModalLabel">{% translate "Historique des versions" %}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate 'Fermer' %}"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="documentHistoryContent" class="row g-3">
|
|
{# Contenu dynamique injecté via fetch #}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# Config JSON pour le JS du formulaire d'ajout #}
|
|
<script type="application/json" id="projectDocumentConfigData">
|
|
{
|
|
"use_managed_documents": {% if use_managed_documents %}true{% else %}false{% endif %},
|
|
"can_add_unique_tag_documents": {% if can_add_unique_tag_documents %}true{% else %}false{% endif %},
|
|
"config_url": "{% url 'projects:projects_document_config' project.id %}"
|
|
}
|
|
</script>
|
|
|
|
{% endblock content %}
|
|
|
|
{% block extra_scripts %}
|
|
<script>
|
|
const project_id = {{ project.id }};
|
|
const document_history_url = "{% url 'projects:projects_document_history' project.id %}";
|
|
|
|
function showAlert(message, alertContainer, type) {
|
|
type = type || "success";
|
|
const alertBox = document.createElement("div");
|
|
alertBox.className = `alert alert-${type} alert-dismissible fade show`;
|
|
alertBox.role = "alert";
|
|
alertBox.innerHTML = `${message}<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>`;
|
|
alertContainer.appendChild(alertBox);
|
|
}
|
|
|
|
const docFileInput = document.getElementById('docFileInput');
|
|
const docFileContainer = document.getElementById('docFileFieldsContainer');
|
|
|
|
let docConfig = { use_managed_documents: false, can_add_unique_tag_documents: false, config_url: null };
|
|
let docAvailableTags = [];
|
|
let docAvailableFolders = [];
|
|
|
|
(function loadDocConfig() {
|
|
try {
|
|
const el = document.getElementById('projectDocumentConfigData');
|
|
if (el) { docConfig = JSON.parse(el.textContent); }
|
|
} catch (e) {}
|
|
if (!docConfig.config_url) return;
|
|
fetch(docConfig.config_url)
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
docConfig.use_managed_documents = data.use_managed_documents;
|
|
docConfig.can_add_unique_tag_documents = data.can_add_unique_tag_documents;
|
|
docAvailableTags = data.tags || [];
|
|
docAvailableFolders = data.folders || [];
|
|
})
|
|
.catch(() => {});
|
|
})();
|
|
|
|
function renderDocFileFields() {
|
|
if (!docFileContainer || !docFileInput) return;
|
|
docFileContainer.innerHTML = '';
|
|
const files = Array.from(docFileInput.files);
|
|
files.forEach((file, index) => {
|
|
const row = document.createElement('div');
|
|
row.classList.add('d-flex', 'align-items-center', 'gap-2', 'mb-2', 'p-2', 'border', 'rounded', 'flex-wrap');
|
|
|
|
const fileName = document.createElement('span');
|
|
fileName.classList.add('flex-grow-1', 'text-truncate');
|
|
fileName.style.maxWidth = '200px';
|
|
fileName.textContent = file.name;
|
|
fileName.title = file.name;
|
|
row.appendChild(fileName);
|
|
|
|
if (docConfig.use_managed_documents && docAvailableTags.length > 0) {
|
|
const tagSel = document.createElement('select');
|
|
tagSel.name = `tag_${index}`;
|
|
tagSel.classList.add('form-select', 'form-select-sm', 'w-auto');
|
|
const def = document.createElement('option');
|
|
def.value = '';
|
|
def.textContent = gettext('-- Sans tag --');
|
|
tagSel.appendChild(def);
|
|
docAvailableTags.forEach(tag => {
|
|
const opt = document.createElement('option');
|
|
opt.value = tag.slug;
|
|
opt.textContent = tag.name;
|
|
opt.dataset.isUnique = tag.is_unique;
|
|
if (tag.is_unique && !docConfig.can_add_unique_tag_documents) {
|
|
opt.disabled = true;
|
|
opt.textContent += ' (non autorisé)';
|
|
}
|
|
tagSel.appendChild(opt);
|
|
});
|
|
row.appendChild(tagSel);
|
|
} else {
|
|
const hidden = document.createElement('input');
|
|
hidden.type = 'hidden';
|
|
hidden.name = `document_type_${index}`;
|
|
hidden.value = 'other';
|
|
row.appendChild(hidden);
|
|
}
|
|
|
|
const delBtn = document.createElement('button');
|
|
delBtn.type = 'button';
|
|
delBtn.classList.add('btn', 'btn-outline-danger', 'btn-sm');
|
|
delBtn.innerHTML = '<i class="bi bi-x-lg"></i>';
|
|
delBtn.onclick = () => {
|
|
const remaining = Array.from(docFileInput.files).filter((_, i) => i !== index);
|
|
const dt = new DataTransfer();
|
|
remaining.forEach(f => dt.items.add(f));
|
|
docFileInput.files = dt.files;
|
|
renderDocFileFields();
|
|
};
|
|
row.appendChild(delBtn);
|
|
docFileContainer.appendChild(row);
|
|
});
|
|
const countInput = document.createElement('input');
|
|
countInput.type = 'hidden';
|
|
countInput.name = 'file_count';
|
|
countInput.value = files.length;
|
|
docFileContainer.appendChild(countInput);
|
|
}
|
|
|
|
if (docFileInput) {
|
|
docFileInput.addEventListener('change', renderDocFileFields);
|
|
}
|
|
|
|
// Historique des versions
|
|
const docHistoryModalEl = document.getElementById('documentHistoryModal');
|
|
if (docHistoryModalEl) {
|
|
docHistoryModalEl.addEventListener('show.bs.modal', function (event) {
|
|
const btn = event.relatedTarget;
|
|
const tagSlug = btn.getAttribute('data-tag-slug');
|
|
const useManaged = btn.getAttribute('data-use-managed') === 'true';
|
|
const uniqueParams = btn.getAttribute('data-unique-params');
|
|
const contentEl = document.getElementById('documentHistoryContent');
|
|
if (contentEl) {
|
|
contentEl.innerHTML = '<div class="text-center py-4"><div class="spinner-border text-primary" role="status"></div></div>';
|
|
}
|
|
if (!useManaged || !tagSlug) return;
|
|
let url = `${document_history_url}?tag_slug=${encodeURIComponent(tagSlug)}`;
|
|
if (uniqueParams && uniqueParams !== '{}' && uniqueParams !== 'null') {
|
|
url += `&unique_params=${encodeURIComponent(uniqueParams)}`;
|
|
}
|
|
fetch(url)
|
|
.then(r => r.json())
|
|
.then(data => { if (contentEl) contentEl.innerHTML = data.html || ''; })
|
|
.catch(() => { if (contentEl) contentEl.innerHTML = '<p class="text-muted">Impossible de charger l\'historique.</p>'; });
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock extra_scripts %}
|
|
|