{% extends 'base.html.twig' %}
{% block body %}
<div class="container mt-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2>
<span id="deviceName">{{ device.name }}</span>
<button class="btn btn-sm btn-outline-secondary ml-2" id="editDeviceNameBtn" title="Renommer">
<i class="fa fa-pencil"></i>
</button>
</h2>
<small class="text-muted">ID : {{ device.deviceId }}{% if device.ip %} - IP : {{ device.ip }}{% endif %} - {{ device.updateDate|date("d/m/Y H:i") }}</small>
</div>
<div>
<button class="btn btn-primary" data-toggle="modal" data-target="#addLinkModal">
<i class="fa fa-plus"></i> Ajouter un lien
</button>
{% if device.linkItems|length == 0 %}
<button class="btn btn-danger ml-2" id="deleteDeviceBtn">
<i class="fa fa-trash"></i> Supprimer le device
</button>
{% endif %}
</div>
</div>
<div id="linksList" class="list-group" style="gap: 0;">
{% for link in links %}
<div class="list-group-item sortable-item {{ link.enabled ? '' : 'disabled-link' }}"
data-link-id="{{ link.id }}"
data-order="{{ link.sortOrder }}"
data-enabled="{{ link.enabled ? '1' : '0' }}"
style="cursor: move;user-select: none; margin-bottom: 8px;padding: 5px 15px;">
<div class="d-flex align-items-center">
<!-- Icône SVG -->
{% if link.svgIcon %}
<div class="mr-3" style="width: 35px; height: 35px; flex-shrink: 0;">
{{ link.svgIcon|raw }}
</div>
{% endif %}
<!-- Contenu principal -->
<div class="flex-grow-1">
<h6>
{{ link.title }}
{% if not link.enabled %}
<span class="badge badge-secondary ml-2">Désactivé</span>
{% endif %}
</h6>
<small class="text-muted">
{{ link.link|slice(0, 30) }}
</small>
</div>
<!-- Actions -->
<div class="ml-3">
<div class="btn-group">
<button class="btn btn-sm btn-outline-{{ link.enabled ? 'warning' : 'success' }} btn-toggle-enabled"
data-link-id="{{ link.id }}"
data-enabled="{{ link.enabled ? '1' : '0' }}"
title="{{ link.enabled ? 'Désactiver' : 'Activer' }}">
<i class="fa fa-{{ link.enabled ? 'eye-slash' : 'eye' }}" aria-hidden="true"></i>
</button>
<button class="btn btn-sm btn-outline-primary btn-edit"
data-link-id="{{ link.id }}"
data-title="{{ link.title }}"
data-link="{{ link.link }}"
data-svg="{{ link.svgIcon }}"
title="Modifier">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
<button class="btn btn-sm btn-outline-danger btn-delete"
data-link-id="{{ link.id }}"
title="Supprimer">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
</div>
{% else %}
<div class="alert alert-info">
Aucun lien pour le moment. Cliquez sur "Ajouter un lien" pour commencer.
</div>
{% endfor %}
</div>
</div>
<!-- Modal Ajouter/Éditer Lien -->
<div class="modal fade" id="addLinkModal" tabindex="-1" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalTitle">Ajouter un lien</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form id="linkForm">
<input type="hidden" id="linkId" value="">
<div class="form-group">
<label for="linkTitle">Titre *</label>
<input type="text" class="form-control" id="linkTitle" required>
</div>
<div class="form-group">
<label for="linkUrl">URL *</label>
<input type="url" class="form-control" id="linkUrl" required>
</div>
<div class="form-group">
<label for="linkSvg">Code SVG</label>
<textarea class="form-control" id="linkSvg" rows="4"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id="saveLinkBtn">Enregistrer</button>
</div>
</div>
</div>
</div>
<!-- Modal Renommer Device -->
<div class="modal fade" id="editDeviceModal" tabindex="-1" role="dialog" aria-labelledby="editDeviceModalTitle" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editDeviceModalTitle">Renommer le device</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form id="deviceNameForm">
<div class="form-group">
<label for="deviceNameInput">Nouveau nom *</label>
<input type="text" class="form-control" id="deviceNameInput" required value="{{ device.name }}">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
<button type="button" class="btn btn-primary" id="saveDeviceNameBtn">Enregistrer</button>
</div>
</div>
</div>
</div>
<style>
.list-group-item {
transition: all 0.2s ease;
}
.list-group-item:hover {
background-color: #555555;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.list-group-item.disabled-link {
opacity: .5;
}
.list-group-item.disabled-link:hover {
background-color: #e9ecef;
}
.sortable-ghost {
opacity: 0.3 !important;
background: #e3f2fd !important;
border: 2px dashed #007bff !important;
}
.sortable-chosen {
background-color: #f8f9fa;
}
.sortable-drag {
opacity: 0.8 !important;
cursor: grabbing !important;
}
.drag-handle:hover {
color: #007bff !important;
}
.sortable-item {
position: relative;
}
#linksList {
min-height: 100px;
}
</style>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script>
const deviceId = {{ device.id }};
let editingLinkId = null;
// SVG par défaut
const defaultSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
</svg>`;
// Initialiser Sortable pour le drag & drop
const linksList = document.getElementById('linksList');
if (linksList) {
const sortable = new Sortable(linksList, {
animation: 200,
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag',
forceFallback: false,
fallbackOnBody: true,
swapThreshold: 0.65,
draggable: '.sortable-item',
filter: '.btn, a, input, textarea, select',
preventOnFilter: true,
onStart: function(evt) {
evt.item.style.opacity = '0.5';
},
onEnd: function(evt) {
evt.item.style.opacity = '1';
if (evt.oldIndex !== evt.newIndex) {
saveOrder();
}
}
});
}
// Fonction pour sauvegarder l'ordre
function saveOrder() {
const orders = {};
document.querySelectorAll('[data-link-id]').forEach((el, index) => {
orders[el.dataset.linkId] = index;
});
fetch(`/kelsync/device/${deviceId}/link/reorder`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orders })
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('Ordre sauvegardé');
}
})
.catch(error => {
console.error('Erreur lors de la sauvegarde:', error);
alert('Erreur lors de la sauvegarde de l\'ordre');
});
}
// Fonction pour fermer la modal
function closeModal(modalId) {
var modal = document.getElementById(modalId);
modal.classList.remove('show');
modal.style.display = 'none';
modal.setAttribute('aria-hidden', 'true');
modal.removeAttribute('aria-modal');
var backdrop = document.querySelector('.modal-backdrop');
if (backdrop) {
backdrop.remove();
}
document.body.classList.remove('modal-open');
}
// Fonction pour ouvrir la modal
function openModal(modalId) {
var modal = document.getElementById(modalId);
modal.classList.add('show');
modal.style.display = 'block';
modal.setAttribute('aria-modal', 'true');
modal.removeAttribute('aria-hidden');
var backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade show';
document.body.appendChild(backdrop);
document.body.classList.add('modal-open');
}
// Gérer les boutons de fermeture
document.querySelectorAll('[data-dismiss="modal"]').forEach(function(btn) {
btn.addEventListener('click', function() {
const modal = btn.closest('.modal');
closeModal(modal.id);
});
});
// Fermer en cliquant sur le backdrop
document.addEventListener('click', function(e) {
if (e.target.classList.contains('modal-backdrop')) {
const openModal = document.querySelector('.modal.show');
if (openModal) {
closeModal(openModal.id);
}
}
});
// Ouvrir modal pour ajouter un lien
document.querySelector('[data-target="#addLinkModal"]').addEventListener('click', function() {
editingLinkId = null;
document.getElementById('modalTitle').textContent = 'Ajouter un lien';
document.getElementById('linkForm').reset();
document.getElementById('linkId').value = '';
document.getElementById('linkSvg').value = defaultSvg;
openModal('addLinkModal');
});
// Ouvrir modal pour éditer un lien
document.addEventListener('click', function(e) {
if (e.target.closest('.btn-edit')) {
const btn = e.target.closest('.btn-edit');
const linkId = btn.dataset.linkId;
editingLinkId = linkId;
document.getElementById('modalTitle').textContent = 'Modifier le lien';
document.getElementById('linkId').value = linkId;
document.getElementById('linkTitle').value = btn.dataset.title || '';
document.getElementById('linkUrl').value = btn.dataset.link || '';
document.getElementById('linkSvg').value = btn.dataset.svg || '';
openModal('addLinkModal');
}
});
// Activer/Désactiver un lien
document.addEventListener('click', function(e) {
if (e.target.closest('.btn-toggle-enabled')) {
const btn = e.target.closest('.btn-toggle-enabled');
const linkId = btn.dataset.linkId;
const currentEnabled = btn.dataset.enabled === '1';
const newEnabled = !currentEnabled;
fetch(`/kelsync/link/${linkId}/toggle`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: newEnabled })
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
}
})
.catch(error => {
console.error('Erreur:', error);
alert('Erreur lors de la modification du statut');
});
}
});
// Supprimer un lien
document.addEventListener('click', function(e) {
if (e.target.closest('.btn-delete')) {
if (confirm('Voulez-vous vraiment supprimer ce lien ?')) {
const linkId = e.target.closest('.btn-delete').dataset.linkId;
fetch(`/kelsync/link/${linkId}/delete`, {
method: 'DELETE'
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
}
})
.catch(error => {
console.error('Erreur:', error);
alert('Erreur lors de la suppression');
});
}
}
});
// Enregistrer le lien
document.getElementById('saveLinkBtn').addEventListener('click', function() {
const form = document.getElementById('linkForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const linkId = document.getElementById('linkId').value;
const data = {
title: document.getElementById('linkTitle').value,
link: document.getElementById('linkUrl').value,
svgIcon: document.getElementById('linkSvg').value
};
const url = linkId
? `/kelsync/link/${linkId}/edit`
: `/kelsync/device/${deviceId}/link/add`;
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.success) {
closeModal('addLinkModal');
location.reload();
}
})
.catch(error => {
alert('Erreur lors de l\'enregistrement');
});
});
// ========== GESTION DU DEVICE ==========
// Ouvrir modal pour renommer le device
document.getElementById('editDeviceNameBtn').addEventListener('click', function() {
openModal('editDeviceModal');
});
// Enregistrer le nouveau nom du device
document.getElementById('saveDeviceNameBtn').addEventListener('click', function() {
const form = document.getElementById('deviceNameForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const newName = document.getElementById('deviceNameInput').value.trim();
if (!newName) {
alert('Le nom ne peut pas être vide');
return;
}
fetch(`/kelsync/device/${deviceId}/edit`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: newName })
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('deviceName').textContent = data.device.name;
closeModal('editDeviceModal');
} else {
alert(data.message || 'Erreur lors de la modification');
}
})
.catch(error => {
console.error('Erreur:', error);
alert('Erreur lors de la modification du nom');
});
});
// Supprimer le device
const deleteBtn = document.getElementById('deleteDeviceBtn');
if (deleteBtn) {
deleteBtn.addEventListener('click', function() {
if (confirm('Voulez-vous vraiment supprimer ce device ?\n\nNote : Le device ne peut être supprimé que s\'il ne contient aucun lien.')) {
fetch(`/kelsync/device/${deviceId}/delete`, {
method: 'DELETE'
})
.then(response => response.json())
.then(data => {
if (data.success) {
window.location.href = '/kelsync/';
} else {
alert(data.message || 'Erreur lors de la suppression');
}
})
.catch(error => {
console.error('Erreur:', error);
alert('Erreur lors de la suppression du device');
});
}
});
}
</script>
{% endblock %}