loko/streetup/assets/static/assets/asset_add_modal.js

636 lines
28 KiB
JavaScript

/**
* Asset Add Modal - JavaScript handler
* Handles the creation of new assets from modal forms
*/
(function() {
'use strict';
// Initialize all add asset modals on the page
document.addEventListener('DOMContentLoaded', function() {
initializeAddAssetModals();
});
function initializeAddAssetModals() {
const forms = document.querySelectorAll('.add-asset-form');
forms.forEach(form => {
const modalId = form.closest('.modal').id;
initializeForm(form, modalId);
});
}
const categoriesCache = {};
function initializeForm(form, modalId) {
const thematicCode = form.dataset.thematic;
const categorySelect = form.querySelector('.asset-category-select');
const modelSelect = form.querySelector('.asset-model-select');
const typeSelect = form.querySelector('.asset-type-select');
const typeContainer = form.querySelector(`#${modalId}TypeContainer`);
const modelRequiredStar = form.querySelector(`#${modalId}ModelRequiredStar`);
const alertContainer = form.querySelector(`#${modalId}Alert`);
const submitBtn = form.querySelector(`#${modalId}Submit`);
// Fetch and cache categories for this thematic
if (thematicCode && !categoriesCache[thematicCode]) {
fetch(`/assets/${thematicCode}/categories/`)
.then(response => response.json())
.then(data => {
if (data.categories) {
categoriesCache[thematicCode] = data.categories;
}
})
.catch(error => {
console.error('Error prefetching categories:', error);
});
}
async function onCategoryChange() {
const categoryId = categorySelect.value;
// Clear & disable model select
modelSelect.innerHTML = '<option value="">' + gettext('Sélectionner d\'abord une catégorie...') + '</option>';
modelSelect.disabled = true;
modelSelect.required = false;
if (modelRequiredStar) modelRequiredStar.style.display = 'none';
if (!categoryId) {
if (typeContainer) typeContainer.classList.add('d-none');
if (typeSelect) {
typeSelect.innerHTML = '<option value="">' + gettext('Sélectionner un type...') + '</option>';
typeSelect.value = '';
typeSelect.required = false;
}
return;
}
// Ensure categories are loaded in cache
if (!categoriesCache[thematicCode]) {
try {
const response = await fetch(`/assets/${thematicCode}/categories/`);
const data = await response.json();
if (data.categories) {
categoriesCache[thematicCode] = data.categories;
}
} catch (error) {
console.error('Error fetching categories:', error);
}
}
const categories = categoriesCache[thematicCode] || [];
const categoryData = categories.find(c => c.id == categoryId);
if (categoryData && categoryData.allowed_models && categoryData.allowed_models.length > 1) {
// Show Type selection
if (typeContainer) typeContainer.classList.remove('d-none');
if (typeSelect) {
typeSelect.innerHTML = '<option value="">' + gettext('Sélectionner un type...') + '</option>';
categoryData.allowed_models.forEach(model => {
const option = document.createElement('option');
option.value = model.model_name;
option.textContent = model.verbose_name;
option.dataset.isModelRequired = model.is_model_required;
typeSelect.appendChild(option);
});
typeSelect.value = '';
typeSelect.required = true;
}
} else {
// Hide Type selection
if (typeContainer) typeContainer.classList.add('d-none');
if (typeSelect) {
typeSelect.innerHTML = '<option value="">' + gettext('Sélectionner un type...') + '</option>';
typeSelect.value = '';
typeSelect.required = false;
}
const singleModel = (categoryData && categoryData.allowed_models && categoryData.allowed_models.length === 1)
? categoryData.allowed_models[0]
: null;
const selectedType = singleModel ? singleModel.model_name : null;
const isModelRequired = singleModel ? singleModel.is_model_required : false;
// Load models for this category
loadModelsForCategory(thematicCode, categoryId, modelSelect, selectedType);
// Set model template requirement
if (isModelRequired) {
if (modelRequiredStar) modelRequiredStar.style.display = '';
modelSelect.required = true;
} else {
if (modelRequiredStar) modelRequiredStar.style.display = 'none';
modelSelect.required = false;
}
}
}
// Handle category change
if (categorySelect && modelSelect) {
categorySelect.addEventListener('change', onCategoryChange);
}
// Handle Type selection change
if (typeSelect) {
typeSelect.addEventListener('change', function() {
const selectedType = this.value;
const categoryId = categorySelect ? categorySelect.value : null;
// Clear & disable model select
modelSelect.innerHTML = '<option value="">' + gettext('Sélectionner d\'abord une catégorie...') + '</option>';
modelSelect.disabled = true;
modelSelect.required = false;
if (modelRequiredStar) modelRequiredStar.style.display = 'none';
if (!categoryId || !selectedType) {
return;
}
// Find the allowed model metadata to check if template is required
const categories = categoriesCache[thematicCode] || [];
const categoryData = categories.find(c => c.id == categoryId);
const allowedModel = categoryData && categoryData.allowed_models
? categoryData.allowed_models.find(m => m.model_name === selectedType)
: null;
const isModelRequired = allowedModel ? allowedModel.is_model_required : false;
// Load models filtered by the selected type
loadModelsForCategory(thematicCode, categoryId, modelSelect, selectedType);
// Set model template requirement
if (isModelRequired) {
if (modelRequiredStar) modelRequiredStar.style.display = '';
modelSelect.required = true;
} else {
if (modelRequiredStar) modelRequiredStar.style.display = 'none';
modelSelect.required = false;
}
});
}
// Handle form submission
form.addEventListener('submit', function(e) {
e.preventDefault();
submitNewAsset(form, modalId, alertContainer, submitBtn);
});
// Reset form when modal is hidden
const modal = document.getElementById(modalId);
if (modal) {
modal.addEventListener('hidden.bs.modal', function() {
resetForm(form, modelSelect, alertContainer);
});
}
}
function loadModelsForCategory(thematicCode, categoryId, modelSelect, selectedType = null) {
modelSelect.innerHTML = '<option value="">' + gettext('Chargement...') + '</option>';
modelSelect.disabled = true;
if (!categoryId) {
modelSelect.innerHTML = '<option value="">' + gettext('Sélectionner d\'abord une catégorie...') + '</option>';
return;
}
const url = `/assets/${thematicCode}/category/${categoryId}/models/`;
fetch(url)
.then(response => response.json())
.then(data => {
modelSelect.innerHTML = '<option value="">' + gettext('Sélectionner un modèle...') + '</option>';
if (data.models && data.models.length > 0) {
let filteredModels = data.models;
if (selectedType) {
filteredModels = data.models.filter(model => {
const mType = model.model_type ? model.model_type.toLowerCase() : '';
const sType = selectedType.toLowerCase();
const mTypeClean = mType.replace('model', '').replace('geo', '');
const sTypeClean = sType.replace('model', '').replace('geo', '');
return mTypeClean === sTypeClean;
});
}
if (filteredModels.length > 0) {
filteredModels.forEach(model => {
const option = document.createElement('option');
option.value = model.id;
option.dataset.modelType = model.model_type;
option.textContent = `${model.name}`;
modelSelect.appendChild(option);
});
modelSelect.disabled = false;
} else {
modelSelect.innerHTML = '<option value="">' + gettext('Aucun modèle disponible pour ce type') + '</option>';
}
} else {
modelSelect.innerHTML = '<option value="">' + gettext('Aucun modèle disponible') + '</option>';
}
})
.catch(error => {
console.error('Error loading models:', error);
modelSelect.innerHTML = '<option value="">' + gettext('Erreur de chargement') + '</option>';
});
}
function submitNewAsset(form, modalId, alertContainer, submitBtn) {
// Disable submit button
const originalBtnText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>' + gettext('Création...');
// Clear previous alerts
alertContainer.classList.add('d-none');
alertContainer.classList.remove('alert-success', 'alert-danger');
// Gather form data
const formData = new FormData(form);
const data = {};
formData.forEach((value, key) => {
data[key] = value;
});
// Resolve asset_type
let assetType = null;
const typeSelect = form.querySelector('.asset-type-select');
if (typeSelect && typeSelect.value) {
assetType = typeSelect.value;
} else {
const modelSelect = form.querySelector('.asset-model-select');
const selectedOption = modelSelect ? modelSelect.options[modelSelect.selectedIndex] : null;
if (selectedOption && selectedOption.dataset.modelType) {
let mType = selectedOption.dataset.modelType.toLowerCase();
if (mType.includes('assetmodel') && !mType.includes('geo')) {
assetType = mType.replace('assetmodel', 'geoasset');
} else {
assetType = mType.replace('model', '');
}
}
}
const categorySelect = form.querySelector('.asset-category-select');
const categoryId = categorySelect ? categorySelect.value : null;
const thematicCode = form.dataset.thematic;
if (!assetType && categoryId && thematicCode) {
const catData = (categoriesCache[thematicCode] || []).find(c => c.id == categoryId);
if (catData && catData.allowed_models && catData.allowed_models.length === 1) {
assetType = catData.allowed_models[0].model_name;
}
}
if (!assetType) {
assetType = getAssetTypeForThematic(thematicCode);
}
data.asset_type = assetType;
const csrfToken = form.querySelector('[name=csrfmiddlewaretoken]').value;
const interventionId = form.dataset.interventionId;
fetch('/assets/api/create-asset/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
if (result.success) {
alertContainer.className = 'alert alert-success';
alertContainer.innerHTML = '<i class="bi bi-check-circle me-1"></i>' + result.message;
alertContainer.classList.remove('d-none');
// Si un asset a été créé et nécessite la sélection d'un template
if (result.needs_template_selection && result.asset_id) {
checkAssetTemplates(interventionId, data.asset_type, result.asset_id, modalId, csrfToken);
} else {
// Redirection normale après un court délai
setTimeout(() => {
performRedirect(modalId, interventionId, result);
}, 1000);
}
} else {
alertContainer.className = 'alert alert-danger';
alertContainer.innerHTML = '<i class="bi bi-exclamation-triangle me-1"></i>' + (result.error || gettext('Erreur lors de la création'));
alertContainer.classList.remove('d-none');
}
})
.catch(error => {
console.error('Error:', error);
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
alertContainer.className = 'alert alert-danger';
alertContainer.innerHTML = '<i class="bi bi-exclamation-triangle me-1"></i>' + gettext('Erreur de connexion');
alertContainer.classList.remove('d-none');
});
}
function checkAssetTemplates(interventionId, assetType, assetId, modalId, csrfToken) {
// Vérifier si l'asset a des templates correspondants
fetch(`/interventions/${interventionId}/check-asset-templates/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({
asset_type: assetType,
asset_id: assetId
})
})
.then(response => response.json())
.then(data => {
if (data.success && data.has_templates && data.available_templates && data.available_templates.length > 0) {
// Afficher le modal de sélection de template
showTemplateSelectionModal(
interventionId,
assetType,
assetId,
data.available_templates,
modalId,
csrfToken,
data.message
);
} else {
// Pas de templates disponibles, rediriger normalement
setTimeout(() => {
performRedirect(modalId, interventionId, {intervention_linked: true});
}, 1000);
}
})
.catch(error => {
console.error('Error checking templates:', error);
// En cas d'erreur, rediriger quand même
setTimeout(() => {
performRedirect(modalId, interventionId, {intervention_linked: true});
}, 1000);
});
}
function showTemplateSelectionModal(interventionId, assetType, assetId, templates, originalModalId, csrfToken, customMessage) {
// Fermer le modal de création d'asset
const originalModal = document.getElementById(originalModalId);
if (originalModal) {
const bsModal = bootstrap.Modal.getInstance(originalModal);
if (bsModal) {
bsModal.hide();
}
}
// Créer un modal pour la sélection du template
const modalHtml = `
<div class="modal fade" id="selectTemplateModal" tabindex="-1" aria-labelledby="selectTemplateModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-primary">
<h5 class="modal-title" id="selectTemplateModalLabel">
<i class="bi bi-list-check me-2"></i>${gettext('Sélection de template d\'opérations')}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${gettext('Fermer')}"></button>
</div>
<div class="modal-body">
<p>${customMessage || gettext('Choisissez le(s) template(s) d\'opérations à appliquer à cet équipement.')}</p>
<div class="mb-3">
<label class="form-label">${gettext('Choisir le(s) template(s) d\'opérations :')}</label>
<div class="border rounded p-3" style="max-height: 300px; overflow-y: auto;">
${templates.map(t => `
<div class="form-check mb-2">
<input class="form-check-input template-checkbox" type="checkbox" value="${t.id}" id="template_${t.id}">
<label class="form-check-label" for="template_${t.id}" title="${t.description}">
<strong>${t.name}</strong>
${t.description ? `<br><small class="text-muted">${t.description}</small>` : ''}
</label>
</div>
`).join('')}
</div>
<div class="mt-2">
<button type="button" class="btn btn-sm btn-outline-secondary" id="selectAllTemplates">
<i class="bi bi-check-all me-1"></i>${gettext('Tout sélectionner')}
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="deselectAllTemplates">
<i class="bi bi-square me-1"></i>${gettext('Tout désélectionner')}
</button>
</div>
</div>
<div id="templateAlert" class="alert d-none"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="skipTemplateBtn">
${gettext('Ignorer et continuer')}
</button>
<button type="button" class="btn btn-primary" id="createOperationsBtn" disabled>
<i class="bi bi-plus-circle me-1"></i>${gettext('Créer les opérations')}
</button>
</div>
</div>
</div>
</div>
`;
// Ajouter le modal au DOM
const modalContainer = document.createElement('div');
modalContainer.innerHTML = modalHtml;
document.body.appendChild(modalContainer);
const selectTemplateModal = document.getElementById('selectTemplateModal');
const templateCheckboxes = selectTemplateModal.querySelectorAll('.template-checkbox');
const createBtn = document.getElementById('createOperationsBtn');
const skipBtn = document.getElementById('skipTemplateBtn');
const templateAlert = document.getElementById('templateAlert');
const selectAllBtn = document.getElementById('selectAllTemplates');
const deselectAllBtn = document.getElementById('deselectAllTemplates');
// Fonction pour vérifier si au moins un template est sélectionné
function updateCreateButtonState() {
const anyChecked = Array.from(templateCheckboxes).some(cb => cb.checked);
createBtn.disabled = !anyChecked;
}
// Activer le bouton de création quand au moins un template est sélectionné
templateCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', updateCreateButtonState);
});
// Bouton "Tout sélectionner"
selectAllBtn.addEventListener('click', function() {
templateCheckboxes.forEach(cb => cb.checked = true);
updateCreateButtonState();
});
// Bouton "Tout désélectionner"
deselectAllBtn.addEventListener('click', function() {
templateCheckboxes.forEach(cb => cb.checked = false);
updateCreateButtonState();
});
// Gérer le clic sur "Ignorer et continuer"
skipBtn.addEventListener('click', function() {
const bsModal = bootstrap.Modal.getInstance(selectTemplateModal);
bsModal.hide();
performRedirect(originalModalId, interventionId, {intervention_linked: true});
});
// Gérer le clic sur "Créer les opérations"
createBtn.addEventListener('click', function() {
const selectedTemplateIds = Array.from(templateCheckboxes)
.filter(cb => cb.checked)
.map(cb => parseInt(cb.value));
if (selectedTemplateIds.length === 0) return;
createBtn.disabled = true;
const originalBtnText = createBtn.innerHTML;
createBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>' + gettext('Création en cours...');
// Appeler l'API pour créer les opérations
fetch(`/interventions/${interventionId}/create-operations-from-template/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({
asset_type: assetType,
asset_id: assetId,
template_ids: selectedTemplateIds
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
templateAlert.className = 'alert alert-success';
templateAlert.innerHTML = '<i class="bi bi-check-circle me-1"></i>' + data.message;
templateAlert.classList.remove('d-none');
// Fermer le modal et rediriger après un court délai
setTimeout(() => {
const bsModal = bootstrap.Modal.getInstance(selectTemplateModal);
bsModal.hide();
performRedirect(originalModalId, interventionId, {intervention_linked: true});
}, 1500);
} else {
createBtn.disabled = false;
createBtn.innerHTML = originalBtnText;
templateAlert.className = 'alert alert-danger';
templateAlert.innerHTML = '<i class="bi bi-exclamation-triangle me-1"></i>' + (data.error || gettext('Erreur lors de la création'));
templateAlert.classList.remove('d-none');
}
})
.catch(error => {
console.error('Error:', error);
createBtn.disabled = false;
createBtn.innerHTML = originalBtnText;
templateAlert.className = 'alert alert-danger';
templateAlert.innerHTML = '<i class="bi bi-exclamation-triangle me-1"></i>' + gettext('Erreur de connexion');
templateAlert.classList.remove('d-none');
});
});
// Nettoyer le modal du DOM après fermeture
selectTemplateModal.addEventListener('hidden.bs.modal', function() {
modalContainer.remove();
});
// Afficher le modal
const bsModal = new bootstrap.Modal(selectTemplateModal);
bsModal.show();
}
function performRedirect(modalId, interventionId, result) {
const modalEl = document.getElementById(modalId);
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) {
modal.hide();
}
// Determine redirect URL
let redirectUrl = null;
// Check if we're on a mobile view
const isMobileView = window.location.pathname.startsWith('/mobile/') ||
document.body.classList.contains('mobile-view') ||
document.querySelector('body')?.getAttribute('data-mobile-view') === 'true';
if (interventionId && result.intervention_linked) {
// If we're in mobile view and there's an intervention, redirect to mobile intervention view
if (isMobileView) {
redirectUrl = `/mobile/interventions/${interventionId}/`;
} else {
redirectUrl = `/interventions/${interventionId}/`;
}
} else if (result.redirect_url) {
redirectUrl = result.redirect_url;
}
// Reload or redirect
if (redirectUrl) {
window.location.href = redirectUrl;
} else {
window.location.reload();
}
}
function getAssetTypeForThematic(thematicCode) {
const mapping = {
'structures': 'structuregeoasset',
'trafficlights': 'trafficlightgeoasset',
'publiclighting': 'publiclightinggeoasset',
'nature': 'naturegeoasset',
'artworks': 'artworksgeoasset',
'controlcenters': 'controlcentergeoasset',
'its': 'itsgeoasset',
'sign': 'signgeoasset',
'roads': 'roadgeoasset',
'publicbuildings': 'publicbuilding',
'buildings': 'publicbuilding'
};
return mapping[thematicCode] || 'geoasset';
}
function resetForm(form, modelSelect, alertContainer) {
form.reset();
const modalId = form.closest('.modal').id;
const typeContainer = form.querySelector(`#${modalId}TypeContainer`);
const typeSelect = form.querySelector('.asset-type-select');
const modelRequiredStar = form.querySelector(`#${modalId}ModelRequiredStar`);
if (typeContainer) typeContainer.classList.add('d-none');
if (typeSelect) {
typeSelect.innerHTML = '<option value="">' + gettext('Sélectionner un type...') + '</option>';
typeSelect.value = '';
typeSelect.required = false;
}
if (modelRequiredStar) modelRequiredStar.style.display = 'none';
if (modelSelect) {
modelSelect.innerHTML = '<option value="">' + gettext('Sélectionner d\'abord une catégorie...') + '</option>';
modelSelect.disabled = true;
modelSelect.required = false;
}
alertContainer.classList.add('d-none');
}
// Helper function for translations (fallback if gettext is not available)
function gettext(text) {
if (typeof window.gettext === 'function') {
return window.gettext(text);
}
return text;
}
// Expose for external use
window.AssetAddModal = {
initialize: initializeAddAssetModals,
loadModelsForCategory: loadModelsForCategory
};
})();