257 lines
13 KiB
HTML
257 lines
13 KiB
HTML
{% load i18n tz controls_custom_filters short_name %}
|
||
|
||
<div class="container mt-4">
|
||
|
||
<!-- Carte MapLibre -->
|
||
|
||
<div id="layer-controls">
|
||
{% for plan in plans %}
|
||
<button class="btn btn-light" onclick="showPlan({{ plan.id }})">{{ plan.name }}</button>
|
||
{% endfor %}
|
||
{% if plans %}
|
||
<button class="btn btn-light" onclick="resetView()">{% translate "Carte" %}</button>
|
||
{% endif %}
|
||
</div>
|
||
<div id="map" class="border-bottom invisible"></div>
|
||
|
||
<!-- Formulaire d'ajout de remarque -->
|
||
<form id="remark-form" method="post" class="mt-3" enctype="multipart/form-data">
|
||
{% csrf_token %}
|
||
|
||
<!-- ✅ Management form du formset (obligatoire) -->
|
||
{{ qty_formset.management_form }}
|
||
|
||
<input type="hidden" name="v" value="{{ request.GET.v }}">
|
||
<input type="hidden" id="control_id" name="control" value="{{ remark.control.id }}">
|
||
|
||
{% if mode == "update" and remark.history.exists %}
|
||
<div class="mb-3">
|
||
<label class="form-label fw-bold">{% translate "Historique des suivis" %} :</label>
|
||
<div class="list-group border rounded p-2 bg-light mb-3" style="max-height: 200px; overflow-y: auto;">
|
||
{% for entry in remark.history.all %}
|
||
<div class="list-group-item bg-transparent border-0 border-bottom py-2 px-1">
|
||
<div class="d-flex align-items-center gap-2 flex-wrap mb-1">
|
||
<span class="small text-muted fw-semibold">📅 {{ entry.created_at|timezone:"Europe/Paris"|date:"d/m/Y H:i" }}</span>
|
||
<span class="badge {{ entry.status|status_badge }}">
|
||
<i class="bi {{ entry.status|status_icon }}"></i>
|
||
{{ entry.get_status_display }}
|
||
</span>
|
||
<span class="small text-muted">({% translate 'par' %} {{ entry.created_by|short_name|default:entry.created_by.username }})</span>
|
||
</div>
|
||
{% if entry.description %}
|
||
<div class="small text-dark" style="white-space: pre-wrap;">{{ entry.description }}</div>
|
||
{% endif %}
|
||
{% if entry.images.exists %}
|
||
<div class="d-flex flex-wrap gap-1 mt-1">
|
||
{% for img in entry.images.all %}
|
||
{% private_file_url img 'thumbnail' as thumbnail_url %}
|
||
<img src="{{ thumbnail_url }}" style="width: 40px; height: 40px; object-fit: cover; border-radius: 4px;">
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
<hr>
|
||
{% endif %}
|
||
|
||
<div id="status-badges" class="d-flex justify-content-center gap-2">
|
||
<span class="badge {% if remark.status == 'information' %}bg-info{% else %}bg-secondary{%endif%} status-badge me-1" data-value="information">Info</span>
|
||
<span class="badge {% if remark.status == 'validated' %}bg-success{% else %}bg-secondary{%endif%} status-badge me-1" data-value="validated">OK</span>
|
||
<span class="badge {% if remark.status == 'to_be_processed' %}bg-danger{% else %}bg-secondary{%endif%} status-badge" data-value="to_be_processed">NOK</span>
|
||
<input type="hidden" name="status" id="selectedStatusInput" value="{{ remark.status }}">
|
||
</div>
|
||
<hr>
|
||
<div class="mb-3">
|
||
<label for="{{ remark_form.description.id_for_label }}" class="form-label">{% translate "Description / Commentaire de suivi" %}:</label>
|
||
{{ remark_form.description }}
|
||
</div>
|
||
<hr>
|
||
|
||
{% if mode == "update" %}
|
||
<div class="mb-3 form-check">
|
||
<input type="checkbox" class="form-check-input" id="id_new_history_entry" name="new_history_entry" checked>
|
||
<label class="form-check-label fw-semibold text-primary" for="id_new_history_entry">
|
||
{% translate "Enregistrer comme un nouveau suivi dans l'historique" %}
|
||
</label>
|
||
<div class="form-text small text-muted">
|
||
{% translate "Décochez uniquement si vous souhaitez corriger une faute ou modifier la saisie précédente sans ajouter de nouvelle étape à l'historique." %}
|
||
</div>
|
||
</div>
|
||
<hr>
|
||
{% endif %}
|
||
<div class="mb-3">
|
||
<label class="form-label">{% translate "Images" %}:</label>
|
||
<div class="d-grid gap-2">
|
||
<button type="button" id="open-camera-modal-btn" class="btn btn-outline-primary">
|
||
<i class="bi bi-camera-fill me-2"></i>{% translate "Prendre des photos (mode continu)" %}
|
||
</button>
|
||
<button type="button" id="select-files-btn" class="btn btn-outline-primary">
|
||
<i class="bi bi-folder2-open me-2"></i>{% translate "Photos, vidéos, fichiers" %}
|
||
</button>
|
||
</div>
|
||
<input type="file" id="id_images" name="images" multiple class="d-none">
|
||
<div id="photo-inputs" class="d-none"></div>
|
||
</div>
|
||
<div id="preview" class="d-flex flex-wrap gap-2"></div>
|
||
<hr>
|
||
<div class="mb-3">
|
||
<label class="form-label">Tags :</label>
|
||
<div id="category-badges" data-selected="{{ selected_category_ids|join:',' }}">
|
||
{% for category in categories %}
|
||
<span
|
||
class="badge category-badge me-1 {% if category.id in selected_category_ids %}bg-primary active{% else %}bg-secondary{% endif %}"
|
||
data-id="{{ category.id }}">
|
||
{{ category.get_name }}
|
||
</span>
|
||
{% endfor %}
|
||
</div>
|
||
<input type="hidden" name="categories" id="selectedCategoriesInput" value="{{ selected_category_ids|join:',' }}">
|
||
</div>
|
||
<hr>
|
||
|
||
<!-- ✅ Récapitulatif des quantités (lisible, pas des inputs) -->
|
||
<div class="mt-3">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<h6 class="m-0">{% translate 'Quantités liées à la remarque' %}</h6>
|
||
<button type="button" class="btn btn-sm btn-outline-primary" id="open-qty-editor">
|
||
<i class="bi bi-plus-square"></i> {% translate 'Ajouter des quantités' %}
|
||
</button>
|
||
</div>
|
||
<div id="qty-summary" class="mt-2">
|
||
<!-- rempli par JS à partir des lignes du formset -->
|
||
<div class="text-muted small" id="qty-summary-empty">
|
||
{% translate "Aucune quantité pour l'instant." %}
|
||
</div>
|
||
<div class="list-group" id="qty-summary-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ✅ Les formulaires réels du formset (inputs) sont *cachés* ici mais restent dans le <form> -->
|
||
<div id="qty-hidden-forms" class="d-none">
|
||
{% for form in qty_formset.forms %}
|
||
<div class="qty-form" data-form-index="{{ forloop.counter0 }}">
|
||
{{ form.as_p }}
|
||
</div>
|
||
{% endfor %}
|
||
<!-- gabarit pour création dynamique -->
|
||
<template id="qty-empty-form-template">
|
||
<div class="qty-form" data-form-index="__prefix__">
|
||
{{ qty_formset.empty_form.as_p }}
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- ✅ Champ caché pour stocker la localisation -->
|
||
<input type="hidden" id="longitude" name="lon">
|
||
<input type="hidden" id="latitude" name="lat">
|
||
<input type="hidden" id="coord_x" name="x">
|
||
<input type="hidden" id="coord_y" name="y">
|
||
<input type="hidden" name="reference_plan" id="reference_plan" value="map">
|
||
|
||
<button id="submit-remark-button" type="submit" name="submit_remark" class="btn btn-primary w-100 mt-3">
|
||
<span id="submit-remark-button-text">
|
||
{% if mode == "update" %} {% translate "Mettre à jour" %} {% else %} {% translate "Enregistrer" %} {% endif %}
|
||
</span>
|
||
<span id="spinner-remark" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||
</button>
|
||
{{ remark_form.control.id }}
|
||
{% if '/mobile/' in request.path %}
|
||
<a href="{% url 'mobile:controls_detail' remark.control.id %}" class="btn btn-secondary mt-3">{% translate "Annuler" %}</a>
|
||
{% else %}
|
||
<a href="{% url 'controls:controls_detail' remark.control.id %}?v={{ request.GET.v }}" class="btn btn-secondary mt-3">{% translate "Annuler" %}</a>
|
||
{% endif %}
|
||
</form>
|
||
|
||
|
||
<!-- ✅ Bottom sheet plein écran: éditeur des quantités (formset UI) -->
|
||
<div id="qty-editor" class="position-fixed top-0 start-0 w-100 h-100 bg-body" style="z-index:1060; display:none;">
|
||
<div class="d-flex align-items-center justify-content-between p-3 border-bottom">
|
||
<h6 class="m-0">{% translate 'Quantités de la remarque' %}</h6>
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" id="close-qty-editor">
|
||
<i class="bi bi-x-lg"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- ⚠️ Pas un formulaire séparé : ce sont des *clones visibles* des inputs déjà dans qty-hidden-forms -->
|
||
<div class="d-flex flex-column h-100">
|
||
<div class="flex-grow-1 overflow-auto p-3 content-scroll">
|
||
<div id="qty-editor-rows" class="vstack gap-3">
|
||
<!-- lignes injectées par JS en clonant les champs du formset -->
|
||
</div>
|
||
<template id="qty-editor-row-template">
|
||
<div class="card shadow-sm p-3" data-form-index="__idx__">
|
||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
<strong>{% translate 'Ligne' %} <span class="row-number">__num__</span></strong>
|
||
<button type="button" class="btn btn-outline-danger btn-sm editor-remove-row">
|
||
<i class="bi bi-trash"></i> {% translate 'Supprimer' %}
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Marché -->
|
||
<div class="mb-2">
|
||
<label class="form-label">{% translate 'Marché (optionnel)' %}</label>
|
||
<div class="field-contract"></div>
|
||
</div>
|
||
|
||
<!-- Poste (compat. avec ton autocomplete : label visible + hidden id si tu les as dans le form) -->
|
||
<div class="mb-2">
|
||
<label class="form-label">{% translate 'Poste (autocomplete si un marché est choisi)' %}</label>
|
||
<div class="field-contract_post_label"></div>
|
||
<div class="field-contract_post_id"></div>
|
||
<div class="list-group mt-1 d-none position-relative" id="qty-post-suggestions-__idx__"></div>
|
||
</div>
|
||
|
||
<!-- Description -->
|
||
<div class="mb-2">
|
||
<label class="form-label">{% translate 'Description (si pas de marché/poste)' %}</label>
|
||
<div class="field-description"></div>
|
||
</div>
|
||
|
||
<!-- q1 q2 q3 -->
|
||
<div class="row g-2">
|
||
<div class="col-4">
|
||
<label class="form-label">{% translate 'Quantité (1)' %}</label>
|
||
<div class="field-q1"></div>
|
||
</div>
|
||
<div class="col-4">
|
||
<label class="form-label">{% translate 'Quantité (2)' %}</label>
|
||
<div class="field-q2"></div>
|
||
</div>
|
||
<div class="col-4">
|
||
<label class="form-label">{% translate 'Quantité (3)' %}</label>
|
||
<div class="field-q3"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row g-2 mt-1">
|
||
<div class="col-6">
|
||
<label class="form-label">{% translate 'Résultat' %}</label>
|
||
<input class="form-control qty-preview" type="text" value="" readonly>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Champ DELETE (si présent dans formset) -->
|
||
<div class="field-DELETE d-none"></div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- Barre d’actions fixe -->
|
||
<div class="action-bar fixed-bottom border-top bg-body">
|
||
<div class="container-fluid p-3">
|
||
<div class="d-grid gap-2">
|
||
<button type="button" class="btn btn-outline-primary btn-lg" id="add-qty-row">
|
||
<i class="bi bi-plus-square"></i> {% translate 'Ajouter une ligne' %}
|
||
</button>
|
||
<button type="button" class="btn btn-primary btn-lg" id="apply-qty-and-close">
|
||
{% translate 'Terminer et revenir' %}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|