refactor: improve inspection document UI with file support, lazy loading, and translation fixes.
This commit is contained in:
parent
1c7e369fe3
commit
d9f1970ca6
11 changed files with 126 additions and 15 deletions
Binary file not shown.
|
|
@ -3658,8 +3658,8 @@ msgid "Actions"
|
||||||
msgstr "Action"
|
msgstr "Action"
|
||||||
|
|
||||||
#: assets/templates/assets/buildings/publicbuilding_detail.html:347
|
#: assets/templates/assets/buildings/publicbuilding_detail.html:347
|
||||||
msgid "Détail de l\\"
|
msgid "Détail de l'asset"
|
||||||
msgstr "Detail of "
|
msgstr "Asset detail"
|
||||||
|
|
||||||
#: assets/templates/assets/buildings/publicbuilding_detail.html:354
|
#: assets/templates/assets/buildings/publicbuilding_detail.html:354
|
||||||
msgid "Aucun asset ou équipement technique enregistré."
|
msgid "Aucun asset ou équipement technique enregistré."
|
||||||
|
|
@ -4377,7 +4377,7 @@ msgstr "No inspection or verification recorded for this equipment."
|
||||||
|
|
||||||
#: assets/templates/assets/components/asset_inspections.html:89
|
#: assets/templates/assets/components/asset_inspections.html:89
|
||||||
msgid "Détail de l'inspection"
|
msgid "Détail de l'inspection"
|
||||||
msgstr "Detail of "
|
msgstr "Inspection detail"
|
||||||
|
|
||||||
#: assets/templates/assets/components/asset_inspections.html:98
|
#: assets/templates/assets/components/asset_inspections.html:98
|
||||||
msgid "Inspection annulée"
|
msgid "Inspection annulée"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -3663,8 +3663,8 @@ msgid "Actions"
|
||||||
msgstr "Actions"
|
msgstr "Actions"
|
||||||
|
|
||||||
#: assets/templates/assets/buildings/publicbuilding_detail.html:347
|
#: assets/templates/assets/buildings/publicbuilding_detail.html:347
|
||||||
msgid "Détail de l\\"
|
msgid "Détail de l'asset"
|
||||||
msgstr "Détail de l\\"
|
msgstr "Détail de l'asset"
|
||||||
|
|
||||||
#: assets/templates/assets/buildings/publicbuilding_detail.html:354
|
#: assets/templates/assets/buildings/publicbuilding_detail.html:354
|
||||||
msgid "Aucun asset ou équipement technique enregistré."
|
msgid "Aucun asset ou équipement technique enregistré."
|
||||||
|
|
@ -4383,7 +4383,7 @@ msgstr "Aucune inspection ni vérification enregistrée pour cet équipement."
|
||||||
|
|
||||||
#: assets/templates/assets/components/asset_inspections.html:89
|
#: assets/templates/assets/components/asset_inspections.html:89
|
||||||
msgid "Détail de l'inspection"
|
msgid "Détail de l'inspection"
|
||||||
msgstr "Détail de l\\"
|
msgstr "Détail de l'inspection"
|
||||||
|
|
||||||
#: assets/templates/assets/components/asset_inspections.html:98
|
#: assets/templates/assets/components/asset_inspections.html:98
|
||||||
msgid "Inspection annulée"
|
msgid "Inspection annulée"
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -3657,8 +3657,8 @@ msgid "Actions"
|
||||||
msgstr "Actie"
|
msgstr "Actie"
|
||||||
|
|
||||||
#: assets/templates/assets/buildings/publicbuilding_detail.html:347
|
#: assets/templates/assets/buildings/publicbuilding_detail.html:347
|
||||||
msgid "Détail de l\\"
|
msgid "Détail de l'asset"
|
||||||
msgstr "Detail van "
|
msgstr "Assetdetail"
|
||||||
|
|
||||||
#: assets/templates/assets/buildings/publicbuilding_detail.html:354
|
#: assets/templates/assets/buildings/publicbuilding_detail.html:354
|
||||||
msgid "Aucun asset ou équipement technique enregistré."
|
msgid "Aucun asset ou équipement technique enregistré."
|
||||||
|
|
@ -4378,7 +4378,7 @@ msgstr "Geen wijzigingen geregistreerd voor dit asset."
|
||||||
|
|
||||||
#: assets/templates/assets/components/asset_inspections.html:89
|
#: assets/templates/assets/components/asset_inspections.html:89
|
||||||
msgid "Détail de l'inspection"
|
msgid "Détail de l'inspection"
|
||||||
msgstr "Detail van "
|
msgstr "Inspectiedetail"
|
||||||
|
|
||||||
#: assets/templates/assets/components/asset_inspections.html:98
|
#: assets/templates/assets/components/asset_inspections.html:98
|
||||||
msgid "Inspection annulée"
|
msgid "Inspection annulée"
|
||||||
|
|
|
||||||
|
|
@ -344,7 +344,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td>{{ eq.next_control_date|date:"d/m/Y"|default:"-" }}</td>
|
<td>{{ eq.next_control_date|date:"d/m/Y"|default:"-" }}</td>
|
||||||
<td class="text-end pe-3">
|
<td class="text-end pe-3">
|
||||||
<a href="{{ eq.get_absolute_url }}" class="btn btn-sm btn-outline-info py-0 px-2 small text-nowrap" title="{% translate 'Détail de l\'asset' %}">
|
<a href="{{ eq.get_absolute_url }}" class="btn btn-sm btn-outline-info py-0 px-2 small text-nowrap" title="{% translate \"Détail de l'asset\" %}">
|
||||||
<i class="bi bi-eye me-1"></i>{% translate "Détail" %}
|
<i class="bi bi-eye me-1"></i>{% translate "Détail" %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -145,11 +145,18 @@
|
||||||
{% if insp.documents.all %}
|
{% if insp.documents.all %}
|
||||||
<div>
|
<div>
|
||||||
<label class="form-label text-muted small fw-semibold mb-2"><i class="bi bi-camera me-1"></i>{% translate "Photos & Documents joints" %} ({{ insp.documents.count }}):</label>
|
<label class="form-label text-muted small fw-semibold mb-2"><i class="bi bi-camera me-1"></i>{% translate "Photos & Documents joints" %} ({{ insp.documents.count }}):</label>
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||||
{% for doc in insp.documents.all %}
|
{% for doc in insp.documents.all %}
|
||||||
<a href="{{ doc.file_url }}" target="_blank" class="d-inline-block border rounded p-1 bg-light">
|
{% if doc.is_image %}
|
||||||
<img data-src="{{ doc.file_url }}" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3C/svg%3E" loading="lazy" alt="Photo" class="rounded" style="width: 75px; height: 75px; object-fit: cover;">
|
<a href="{{ doc.file_url }}" target="_blank" class="d-inline-block border rounded p-1 bg-light" title="{{ doc.filename }}">
|
||||||
|
<img data-src="{{ doc.file_url }}" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3C/svg%3E" loading="lazy" alt="{{ doc.filename }}" class="rounded" style="width: 75px; height: 75px; object-fit: cover;">
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ doc.file_url }}" target="_blank" class="btn btn-sm btn-outline-secondary d-inline-flex align-items-center gap-1" title="{{ doc.filename }}">
|
||||||
|
<i class="bi bi-file-earmark-arrow-down text-primary"></i>
|
||||||
|
<span class="text-truncate" style="max-width: 160px;">{{ doc.filename }}</span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -260,7 +267,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
if (alertBox) {
|
if (alertBox) {
|
||||||
alertBox.textContent = data.error || "{% filter escapejs %}{% translate 'Erreur lors de l\'enregistrement.' %}{% endfilter %}";
|
{% translate "Erreur lors de l'enregistrement." as err_save_msg %}
|
||||||
|
alertBox.textContent = data.error || "{{ err_save_msg|escapejs }}";
|
||||||
alertBox.classList.remove('d-none');
|
alertBox.classList.remove('d-none');
|
||||||
}
|
}
|
||||||
if (submitBtn) submitBtn.disabled = false;
|
if (submitBtn) submitBtn.disabled = false;
|
||||||
|
|
@ -279,6 +287,25 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
(function() {
|
||||||
|
function loadLazyModalImages(modal) {
|
||||||
|
if (!modal || typeof modal.querySelectorAll !== 'function') return;
|
||||||
|
modal.querySelectorAll('img[data-src]').forEach(function(img) {
|
||||||
|
const dataSrc = img.getAttribute('data-src');
|
||||||
|
if (dataSrc) {
|
||||||
|
img.src = dataSrc;
|
||||||
|
img.removeAttribute('data-src');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.addEventListener('show.bs.modal', function(e) {
|
||||||
|
loadLazyModalImages(e.target);
|
||||||
|
});
|
||||||
|
document.addEventListener('shown.bs.modal', function(e) {
|
||||||
|
loadLazyModalImages(e.target);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
window.handleToggleCancelAssetInspection = function(inspectionId, action) {
|
window.handleToggleCancelAssetInspection = function(inspectionId, action) {
|
||||||
const confirmMsg = action === 'cancel'
|
const confirmMsg = action === 'cancel'
|
||||||
? "{% filter escapejs %}{% translate 'Êtes-vous sûr de vouloir annuler cette inspection ?' %}{% endfilter %}"
|
? "{% filter escapejs %}{% translate 'Êtes-vous sûr de vouloir annuler cette inspection ?' %}{% endfilter %}"
|
||||||
|
|
|
||||||
|
|
@ -156,3 +156,48 @@ class ParkingSpotAndInspectionTest(TestCase):
|
||||||
self.assertEqual(config["parking"]["assets_geojson_url"], reverse("assets:parking_assets_geojson"))
|
self.assertEqual(config["parking"]["assets_geojson_url"], reverse("assets:parking_assets_geojson"))
|
||||||
self.assertEqual(config["parking"]["locations_geojson_url"], reverse("assets:parking_locations_geojson"))
|
self.assertEqual(config["parking"]["locations_geojson_url"], reverse("assets:parking_locations_geojson"))
|
||||||
|
|
||||||
|
def test_inspection_modal_title_and_thumbnails(self):
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
|
from inspections.models import InspectionDocument
|
||||||
|
from django.utils.translation import activate
|
||||||
|
|
||||||
|
self.user_config.is_intern = True
|
||||||
|
self.user_config.save()
|
||||||
|
activate('fr')
|
||||||
|
# Create an inspection on this parking spot
|
||||||
|
inspection = ParkingSpotInspection.objects.create(
|
||||||
|
asset=self.spot,
|
||||||
|
inspector=self.user,
|
||||||
|
inspection_date=timezone.now(),
|
||||||
|
result_status="compliant",
|
||||||
|
verified_standard_stand_count=3,
|
||||||
|
notes="Inspection vélo conforme"
|
||||||
|
)
|
||||||
|
|
||||||
|
photo = SimpleUploadedFile("arceau_velo.jpg", b"\xff\xd8\xff\xe0testimage", content_type="image/jpeg")
|
||||||
|
doc_img = InspectionDocument.objects.create(inspection=inspection, file=photo)
|
||||||
|
|
||||||
|
url = reverse('assets:parking_assets_detail', kwargs={'asset_model': 'parkingspot', 'asset_id': self.spot.id})
|
||||||
|
|
||||||
|
# Test in French
|
||||||
|
self.client.cookies['django_language'] = 'fr'
|
||||||
|
response_fr = self.client.get(url)
|
||||||
|
self.assertEqual(response_fr.status_code, 200)
|
||||||
|
content_fr = response_fr.content.decode('utf-8')
|
||||||
|
self.assertIn("Détail de l'inspection", content_fr)
|
||||||
|
self.assertNotIn("Détail de \\", content_fr)
|
||||||
|
|
||||||
|
# Test in English
|
||||||
|
self.client.cookies['django_language'] = 'en'
|
||||||
|
response_en = self.client.get(url)
|
||||||
|
self.assertEqual(response_en.status_code, 200)
|
||||||
|
content_en = response_en.content.decode('utf-8')
|
||||||
|
self.assertIn("Inspection detail", content_en)
|
||||||
|
|
||||||
|
# Check thumbnail rendering
|
||||||
|
self.assertIn('data-src="', content_fr)
|
||||||
|
self.assertIn(doc_img.filename, content_fr)
|
||||||
|
self.assertIn('show.bs.modal', content_fr)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -372,6 +372,25 @@ if ('serviceWorker' in navigator) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Lazy loading des images dans les modales Bootstrap
|
||||||
|
(function() {
|
||||||
|
function loadLazyModalImages(modal) {
|
||||||
|
if (!modal || typeof modal.querySelectorAll !== 'function') return;
|
||||||
|
modal.querySelectorAll('img[data-src]').forEach(function(img) {
|
||||||
|
const dataSrc = img.getAttribute('data-src');
|
||||||
|
if (dataSrc) {
|
||||||
|
img.src = dataSrc;
|
||||||
|
img.removeAttribute('data-src');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.addEventListener('show.bs.modal', function(e) {
|
||||||
|
loadLazyModalImages(e.target);
|
||||||
|
});
|
||||||
|
document.addEventListener('shown.bs.modal', function(e) {
|
||||||
|
loadLazyModalImages(e.target);
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- MapLibre GL 5.7.0 (local) -->
|
<!-- MapLibre GL 5.7.0 (local) -->
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,26 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Lazy loading des images dans les modales Bootstrap
|
||||||
|
(function() {
|
||||||
|
function loadLazyModalImages(modal) {
|
||||||
|
if (!modal || typeof modal.querySelectorAll !== 'function') return;
|
||||||
|
modal.querySelectorAll('img[data-src]').forEach(function(img) {
|
||||||
|
const dataSrc = img.getAttribute('data-src');
|
||||||
|
if (dataSrc) {
|
||||||
|
img.src = dataSrc;
|
||||||
|
img.removeAttribute('data-src');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.addEventListener('show.bs.modal', function(e) {
|
||||||
|
loadLazyModalImages(e.target);
|
||||||
|
});
|
||||||
|
document.addEventListener('shown.bs.modal', function(e) {
|
||||||
|
loadLazyModalImages(e.target);
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="{% static 'common/help_panel.js' %}"></script>
|
<script src="{% static 'common/help_panel.js' %}"></script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue