/** * Linux BenchTools - Peripheral Detail Page */ let peripheralId = null; let peripheral = null; let editMode = false; // Initialize document.addEventListener('DOMContentLoaded', () => { // Get peripheral ID from URL const urlParams = new URLSearchParams(window.location.search); peripheralId = parseInt(urlParams.get('id')); if (!peripheralId) { showError('ID périphérique invalide'); setTimeout(() => window.location.href = 'peripherals.html', 2000); return; } loadPeripheral(); loadPhotos(); loadDocuments(); loadLinks(); loadHistory(); const photoUrlInput = document.getElementById('photo-url'); if (photoUrlInput) { photoUrlInput.addEventListener('input', updatePhotoUrlButton); } const editUtilisation = document.getElementById('edit-utilisation'); if (editUtilisation) { editUtilisation.addEventListener('change', updateEditUtilisationFields); } }); // Load peripheral details async function loadPeripheral() { try { peripheral = await apiRequest(`/peripherals/${peripheralId}`); displayPeripheral(peripheral); } catch (error) { console.error('Error loading peripheral:', error); showError('Erreur lors du chargement du périphérique'); } } // Display peripheral information function displayPeripheral(p) { // Update page title document.getElementById('peripheral-name').textContent = p.nom; document.title = `${p.nom} - Linux BenchTools`; // Main info document.getElementById('type_principal').textContent = p.type_principal || '-'; document.getElementById('sous_type').textContent = p.sous_type || '-'; document.getElementById('marque').textContent = p.marque || '-'; document.getElementById('modele').textContent = p.modele || '-'; document.getElementById('numero_serie').textContent = p.numero_serie || '-'; // USB info - show only if present if (p.vendor_id) { document.getElementById('vendor_id').textContent = p.vendor_id; document.getElementById('vendor-id-item').style.display = 'block'; } else { document.getElementById('vendor-id-item').style.display = 'none'; } if (p.product_id) { document.getElementById('product_id').textContent = p.product_id; document.getElementById('product-id-item').style.display = 'block'; } else { document.getElementById('product-id-item').style.display = 'none'; } if (p.usb_device_id) { document.getElementById('usb_device_id').textContent = p.usb_device_id; document.getElementById('usb-device-id-item').style.display = 'block'; } else { document.getElementById('usb-device-id-item').style.display = 'none'; } if (p.fabricant) { document.getElementById('fabricant').textContent = p.fabricant; document.getElementById('fabricant-item').style.display = 'block'; } else { document.getElementById('fabricant-item').style.display = 'none'; } if (p.produit) { document.getElementById('produit').textContent = p.produit; document.getElementById('produit-item').style.display = 'block'; } else { document.getElementById('produit-item').style.display = 'none'; } const etatSpan = document.getElementById('etat'); if (p.etat) { etatSpan.innerHTML = `${p.etat}`; } else { etatSpan.textContent = '-'; } document.getElementById('rating').innerHTML = renderStars(p.rating || 0); document.getElementById('quantite_disponible').textContent = `${p.quantite_disponible || 0} / ${p.quantite_totale || 0}`; // Purchase info document.getElementById('boutique').textContent = p.boutique || '-'; document.getElementById('date_achat').textContent = p.date_achat ? formatDate(p.date_achat) : '-'; document.getElementById('prix').textContent = p.prix ? `${p.prix.toFixed(2)} ${p.devise || 'EUR'}` : '-'; let garantieText = '-'; if (p.garantie_duree_mois) { garantieText = `${p.garantie_duree_mois} mois`; if (p.garantie_expiration) { garantieText += ` (exp: ${formatDate(p.garantie_expiration)})`; } } document.getElementById('garantie').textContent = garantieText; // Location if (p.location_id) { document.getElementById('location').textContent = `Location #${p.location_id}`; } else if (p.location_details) { document.getElementById('location').textContent = p.location_details; } else { document.getElementById('location').textContent = '-'; } document.getElementById('location_details').textContent = p.location_details || '-'; if (p.connecte_a) { document.getElementById('connecte_a').textContent = p.connecte_a; document.getElementById('connecte_a-item').style.display = 'block'; } else { document.getElementById('connecte_a-item').style.display = 'none'; } // Notes document.getElementById('notes').textContent = p.notes || 'Aucune note'; attachCopyButtons(); } // Load photos async function loadPhotos() { try { const photos = await apiRequest(`/peripherals/${peripheralId}/photos`); displayPhotos(photos); } catch (error) { console.error('Error loading photos:', error); } } // Display photos function displayPhotos(photos) { const grid = document.getElementById('photos-grid'); if (photos.length === 0) { grid.innerHTML = '

Aucune photo

'; return; } grid.innerHTML = photos.map(photo => `
${escapeHtml(photo.description || 'Photo')} ${photo.is_primary ? ' Principale' : ''}
`).join(''); } // Load documents async function loadDocuments() { try { const documents = await apiRequest(`/peripherals/${peripheralId}/documents`); displayDocuments(documents); } catch (error) { console.error('Error loading documents:', error); } } // Display documents function displayDocuments(documents) { const list = document.getElementById('documents-list'); if (documents.length === 0) { list.innerHTML = '

Aucun document

'; return; } list.innerHTML = documents.map(doc => `
${escapeHtml(doc.filename)} ${getDocTypeLabel(doc.doc_type)} - ${formatBytes(doc.size_bytes || 0)} ${doc.description ? `

${escapeHtml(doc.description)}

` : ''}
`).join(''); } // Load links async function loadLinks() { try { const links = await apiRequest(`/peripherals/${peripheralId}/links`); displayLinks(links); } catch (error) { console.error('Error loading links:', error); } } // Display links function displayLinks(links) { const list = document.getElementById('links-list'); if (links.length === 0) { list.innerHTML = '

Aucun lien

'; return; } list.innerHTML = links.map(link => ` `).join(''); } // Load history async function loadHistory() { try { const history = await apiRequest(`/peripherals/${peripheralId}/history`); displayHistory(history); } catch (error) { console.error('Error loading history:', error); } } // Display history function displayHistory(history) { const list = document.getElementById('history-list'); if (history.length === 0) { list.innerHTML = '

Aucun historique

'; return; } list.innerHTML = `
${history.map(h => `
${getHistoryActionLabel(h.action)} ${formatDateTime(h.timestamp)} ${h.user ? `par ${escapeHtml(h.user)}` : ''} ${h.notes ? `

${escapeHtml(h.notes)}

` : ''}
`).join('')}
`; } // Set photo as primary async function setPrimaryPhoto(photoId) { try { await apiRequest(`/peripherals/${peripheralId}/photos/${photoId}/set-primary`, { method: 'POST' }); showSuccess('Photo principale définie'); loadPhotos(); // Reload to update icons } catch (error) { console.error('Error setting primary photo:', error); showError('Erreur lors de la définition de la photo principale'); } } // Upload photo async function uploadPhoto(event) { event.preventDefault(); const formData = new FormData(event.target); const file = document.getElementById('photo-file').files[0]; const imageUrl = (document.getElementById('photo-url').value || '').trim(); if (!file && !imageUrl) { showError('Veuillez choisir un fichier ou fournir une URL'); return; } if (imageUrl && !file) { await uploadPhotoFromUrl(); return; } try { await apiRequest(`/peripherals/${peripheralId}/photos`, { method: 'POST', body: formData }); closeModal('modal-upload-photo'); showSuccess('Photo ajoutée avec succès'); loadPhotos(); } catch (error) { console.error('Error uploading photo:', error); showError('Erreur lors de l\'upload de la photo'); } } function updatePhotoUrlButton() { const imageUrl = (document.getElementById('photo-url').value || '').trim(); const button = document.getElementById('btn-upload-url'); if (!button) return; button.disabled = !/^https?:\/\//i.test(imageUrl); } async function uploadPhotoFromUrl() { const imageUrl = (document.getElementById('photo-url').value || '').trim(); if (!/^https?:\/\//i.test(imageUrl)) { showError('URL invalide (http/https requis)'); return; } const formData = new FormData(); formData.append('image_url', imageUrl); formData.append('description', document.getElementById('photo-description').value || ''); formData.append('is_primary', document.getElementById('photo-primary').checked ? 'true' : 'false'); try { await apiRequest(`/peripherals/${peripheralId}/photos/from-url`, { method: 'POST', body: formData }); closeModal('modal-upload-photo'); document.getElementById('form-upload-photo').reset(); updatePhotoUrlButton(); showSuccess('Photo importée depuis URL'); loadPhotos(); } catch (error) { console.error('Error importing photo from URL:', error); showError(error.message || 'Erreur lors de l\'import de l\'URL'); } } // Upload document async function uploadDocument(event) { event.preventDefault(); const formData = new FormData(event.target); try { await apiRequest(`/peripherals/${peripheralId}/documents`, { method: 'POST', body: formData }); closeModal('modal-upload-document'); showSuccess('Document ajouté avec succès'); loadDocuments(); } catch (error) { console.error('Error uploading document:', error); showError('Erreur lors de l\'upload du document'); } } // Add link async function addLink(event) { event.preventDefault(); const formData = new FormData(event.target); const data = { peripheral_id: peripheralId }; for (let [key, value] of formData.entries()) { data[key] = value; } try { await apiRequest(`/peripherals/${peripheralId}/links`, { method: 'POST', body: JSON.stringify(data) }); closeModal('modal-add-link'); showSuccess('Lien ajouté avec succès'); loadLinks(); } catch (error) { console.error('Error adding link:', error); showError('Erreur lors de l\'ajout du lien'); } } // Delete functions async function deletePhoto(photoId) { if (!confirm('Supprimer cette photo ?')) return; try { await apiRequest(`/peripherals/photos/${photoId}`, { method: 'DELETE' }); showSuccess('Photo supprimée'); loadPhotos(); } catch (error) { showError('Erreur lors de la suppression'); } } async function deleteDocument(docId) { if (!confirm('Supprimer ce document ?')) return; try { await apiRequest(`/peripherals/documents/${docId}`, { method: 'DELETE' }); showSuccess('Document supprimé'); loadDocuments(); } catch (error) { showError('Erreur lors de la suppression'); } } async function deleteLink(linkId) { if (!confirm('Supprimer ce lien ?')) return; try { await apiRequest(`/peripherals/links/${linkId}`, { method: 'DELETE' }); showSuccess('Lien supprimé'); loadLinks(); } catch (error) { showError('Erreur lors de la suppression'); } } async function deletePeripheral() { if (!confirm('Êtes-vous sûr de vouloir supprimer ce périphérique ?')) return; try { await apiRequest(`/peripherals/${peripheralId}`, { method: 'DELETE' }); showSuccess('Périphérique supprimé'); setTimeout(() => window.location.href = 'peripherals.html', 1500); } catch (error) { showError('Erreur lors de la suppression'); } } // Modal functions function showUploadPhotoModal() { document.getElementById('modal-upload-photo').style.display = 'block'; } function showUploadDocumentModal() { document.getElementById('modal-upload-document').style.display = 'block'; } function showAddLinkModal() { document.getElementById('modal-add-link').style.display = 'block'; } function closeModal(modalId) { document.getElementById(modalId).style.display = 'none'; } // Helper functions function getEtatClass(etat) { const classes = { 'Neuf': 'success', 'Bon': 'info', 'Usagé': 'warning', 'Défectueux': 'danger', 'Retiré': 'secondary' }; return classes[etat] || 'secondary'; } function renderStars(rating) { const fullStars = Math.floor(rating); const hasHalfStar = rating % 1 >= 0.5; const emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0); let html = ''; for (let i = 0; i < fullStars; i++) html += ''; if (hasHalfStar) html += ''; for (let i = 0; i < emptyStars; i++) html += ''; return html; } function attachCopyButtons() { const items = document.querySelectorAll('.info-item'); items.forEach(item => { const existing = item.querySelector('.copy-field-btn'); if (existing) existing.remove(); if (item.style.display === 'none') { return; } const valueEl = item.querySelector('span'); if (!valueEl) return; const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'copy-field-btn'; btn.innerHTML = 'Copié'; btn.addEventListener('click', async (event) => { event.stopPropagation(); const text = (valueEl.innerText || '').trim(); if (!text || text === '-') { showError('Rien à copier'); return; } try { await copyTextToClipboard(text); btn.classList.add('copied'); setTimeout(() => btn.classList.remove('copied'), 1500); showSuccess('Copié'); } catch (error) { console.error('Copy failed:', error); showError('Copie impossible'); } }); item.appendChild(btn); }); } async function copyTextToClipboard(text) { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); return; } // Fallback for non-HTTPS / older browsers const input = document.createElement('input'); input.value = text; document.body.appendChild(input); input.select(); input.setSelectionRange(0, input.value.length); const ok = document.execCommand('copy'); document.body.removeChild(input); if (!ok) { throw new Error('execCommand copy failed'); } } function getDocIcon(docType) { const icons = { 'manual': 'pdf', 'warranty': 'certificate', 'invoice': 'file-invoice', 'datasheet': 'file-code', 'other': 'file' }; return icons[docType] || 'file'; } function getDocTypeLabel(docType) { const labels = { 'manual': 'Manuel', 'warranty': 'Garantie', 'invoice': 'Facture', 'datasheet': 'Fiche technique', 'other': 'Autre' }; return labels[docType] || docType; } function getLinkIcon(linkType) { const icons = { 'manufacturer': 'industry', 'support': 'life-ring', 'drivers': 'download', 'documentation': 'book', 'custom': 'link' }; return icons[linkType] || 'link'; } function getLinkTypeLabel(linkType) { const labels = { 'manufacturer': 'Fabricant', 'support': 'Support', 'drivers': 'Drivers', 'documentation': 'Documentation', 'custom': 'Personnalisé' }; return labels[linkType] || linkType; } function getHistoryIcon(action) { const icons = { 'created': 'plus', 'moved': 'arrows-alt', 'assigned': 'link', 'unassigned': 'unlink', 'stored': 'box' }; return icons[action] || 'circle'; } function getHistoryActionLabel(action) { const labels = { 'created': 'Créé', 'moved': 'Déplacé', 'assigned': 'Assigné', 'unassigned': 'Désassigné', 'stored': 'Stocké' }; return labels[action] || action; } function toggleEditMode() { if (!peripheral) { showError('Aucun périphérique chargé'); return; } // Populate form with peripheral data document.getElementById('edit-nom').value = peripheral.nom || ''; document.getElementById('edit-type_principal').value = peripheral.type_principal || ''; document.getElementById('edit-sous_type').value = peripheral.sous_type || ''; document.getElementById('edit-marque').value = peripheral.marque || ''; document.getElementById('edit-modele').value = peripheral.modele || ''; document.getElementById('edit-numero_serie').value = peripheral.numero_serie || ''; // USB info document.getElementById('edit-vendor_id').value = peripheral.vendor_id || ''; document.getElementById('edit-product_id').value = peripheral.product_id || ''; document.getElementById('edit-usb_device_id').value = peripheral.usb_device_id || ''; document.getElementById('edit-fabricant').value = peripheral.fabricant || ''; document.getElementById('edit-produit').value = peripheral.produit || ''; document.getElementById('edit-boutique').value = peripheral.boutique || ''; document.getElementById('edit-date_achat').value = peripheral.date_achat || ''; document.getElementById('edit-prix').value = peripheral.prix || ''; document.getElementById('edit-devise').value = peripheral.devise || 'EUR'; document.getElementById('edit-garantie_duree_mois').value = peripheral.garantie_duree_mois || ''; document.getElementById('edit-etat').value = peripheral.etat || 'Neuf'; setEditRating(peripheral.rating || 0); document.getElementById('edit-quantite_totale').value = peripheral.quantite_totale || 1; document.getElementById('edit-quantite_disponible').value = peripheral.quantite_disponible || 1; const utilisationValue = peripheral.connecte_a ? 'utilise' : 'non_utilise'; document.getElementById('edit-utilisation').value = utilisationValue; document.getElementById('edit-synthese').value = peripheral.synthese || ''; document.getElementById('edit-cli_yaml').value = peripheral.cli_yaml || ''; document.getElementById('edit-cli_raw').value = peripheral.cli_raw || ''; document.getElementById('edit-specifications').value = peripheral.specifications || ''; document.getElementById('edit-notes').value = peripheral.notes || ''; // Load and set location loadEditLocations(peripheral.location_id); // Load and set boutique loadEditBoutiques(peripheral.boutique); // Load and set host loadEditHosts(peripheral.connecte_a); updateEditUtilisationFields(); loadEditStockageLocations(peripheral.location_details); // Show modal document.getElementById('modal-edit').style.display = 'block'; } // Load hosts for edit modal async function loadEditHosts(selectedHost) { try { const result = await apiRequest('/peripherals/config/hosts'); if (!result.success) return; const select = document.getElementById('edit-connecte_a'); if (!select) return; select.innerHTML = ''; result.hosts.forEach(host => { const option = document.createElement('option'); option.value = host.nom; option.textContent = host.localisation ? `${host.nom} (${host.localisation})` : host.nom; if (host.nom === selectedHost) { option.selected = true; } select.appendChild(option); }); if (selectedHost && !result.hosts.find(h => h.nom === selectedHost)) { const option = document.createElement('option'); option.value = selectedHost; option.textContent = selectedHost; option.selected = true; select.appendChild(option); } } catch (error) { console.error('Error loading hosts:', error); } } // Load storage locations for edit modal async function loadEditStockageLocations(selectedLocation) { try { const result = await apiRequest('/peripherals/config/stockage-locations'); if (!result.success) return; const select = document.getElementById('edit-stockage_location'); if (!select) return; select.innerHTML = ''; result.locations.forEach(name => { const option = document.createElement('option'); option.value = name; option.textContent = name; if (name === selectedLocation) { option.selected = true; } select.appendChild(option); }); if (selectedLocation && !result.locations.includes(selectedLocation)) { const option = document.createElement('option'); option.value = selectedLocation; option.textContent = selectedLocation; option.selected = true; select.appendChild(option); } } catch (error) { console.error('Error loading storage locations:', error); } } function updateEditUtilisationFields() { const utilisation = document.getElementById('edit-utilisation')?.value || 'non_utilise'; const hostGroup = document.getElementById('edit-group-connecte_a'); const locationGroup = document.getElementById('edit-group-location_id'); const stockageGroup = document.getElementById('edit-group-stockage_location'); if (utilisation === 'utilise') { if (hostGroup) hostGroup.style.display = 'block'; if (locationGroup) locationGroup.style.display = 'none'; if (stockageGroup) stockageGroup.style.display = 'none'; } else { if (hostGroup) hostGroup.style.display = 'none'; if (locationGroup) locationGroup.style.display = 'none'; if (stockageGroup) stockageGroup.style.display = 'block'; } } // Load boutiques for edit modal async function loadEditBoutiques(selectedBoutique) { try { const result = await apiRequest('/peripherals/config/boutiques'); if (!result.success) return; const select = document.getElementById('edit-boutique'); if (!select) return; select.innerHTML = ''; result.boutiques.forEach(name => { const option = document.createElement('option'); option.value = name; option.textContent = name; if (name === selectedBoutique) { option.selected = true; } select.appendChild(option); }); if (selectedBoutique && !result.boutiques.includes(selectedBoutique)) { const option = document.createElement('option'); option.value = selectedBoutique; option.textContent = selectedBoutique; option.selected = true; select.appendChild(option); } } catch (error) { console.error('Error loading boutiques:', error); } } // Load locations for edit modal async function loadEditLocations(selectedLocationId) { try { const locations = await apiRequest('/locations/'); const select = document.getElementById('edit-location_id'); select.innerHTML = ''; locations.forEach(location => { const option = document.createElement('option'); option.value = location.id; option.textContent = location.nom; if (location.id === selectedLocationId) { option.selected = true; } select.appendChild(option); }); } catch (error) { console.error('Error loading locations:', error); } } function closeEditModal() { document.getElementById('modal-edit').style.display = 'none'; } function setEditRating(rating) { const stars = document.querySelectorAll('#edit-star-rating .fa-star'); const ratingInput = document.getElementById('edit-rating'); ratingInput.value = rating; stars.forEach((star, index) => { if (index < rating) { star.classList.add('active'); } else { star.classList.remove('active'); } }); } // Star rating click handler for edit form document.addEventListener('DOMContentLoaded', () => { const editStars = document.querySelectorAll('#edit-star-rating .fa-star'); editStars.forEach(star => { star.addEventListener('click', () => { const rating = parseInt(star.getAttribute('data-rating')); setEditRating(rating); }); }); }); async function savePeripheral(event) { event.preventDefault(); const form = event.target; const formData = new FormData(form); const data = {}; // Convert FormData to object for (let [key, value] of formData.entries()) { // Convert numeric fields if (key === 'utilisation') { continue; } else if (['prix', 'garantie_duree_mois', 'quantite_totale', 'quantite_disponible', 'rating'].includes(key)) { data[key] = value ? parseFloat(value) : null; } else { data[key] = value || null; } } const utilisation = document.getElementById('edit-utilisation')?.value || 'non_utilise'; if (utilisation === 'utilise') { data.connecte_a = document.getElementById('edit-connecte_a')?.value || null; data.location_id = null; const hostSelect = document.getElementById('edit-connecte_a'); const hostText = hostSelect?.selectedOptions?.[0]?.textContent || ''; const match = hostText.match(/\((.+)\)$/); if (match && match[1]) { data.location_details = match[1]; } } else { data.connecte_a = null; data.location_details = null; data.location_id = null; const stockage = document.getElementById('edit-stockage_location')?.value || null; data.location_details = stockage; } try { const response = await apiRequest(`/peripherals/${peripheralId}`, { method: 'PUT', body: JSON.stringify(data) }); showSuccess('Périphérique mis à jour avec succès'); closeEditModal(); // Reload peripheral data await loadPeripheral(); } catch (error) { console.error('Error updating peripheral:', error); showError('Erreur lors de la mise à jour du périphérique'); } } // Close modal when clicking outside window.onclick = function(event) { if (event.target.classList.contains('modal')) { event.target.style.display = 'none'; } }