Ajout des fonctionnalités de sélection multiple et unification de l'authentification
## Nouvelles fonctionnalités ### Sélection multiple et actions en lot - Ajout d'une colonne de checkboxes avec case "Tout sélectionner" - Panneau d'actions en lot (édition et suppression de plusieurs éléments) - Modals dédiées pour l'édition et suppression en lot - Gestion intelligente de la sélection (état indéterminé) - Routes serveur `/bulk-edit` et `/bulk-delete` avec validation sécurisée ### Amélioration des modals de confirmation - Modal de confirmation pour le renvoi (remplace le confirm() basique) - Interface cohérente avec les autres modals - Gestion clavier (Escape/Enter) pour toutes les modals ### Unification du système d'authentification - Fusion des deux systèmes de login (DB + config) en une seule route - Priorité à la base de données avec fallback sur le fichier config - Logs détaillés avec émojis pour faciliter le débogage - Robustesse améliorée (admin de secours si DB en panne) ## Améliorations configuration et posteur - Configuration API pour le renvoi vers le site principal (config.js) - Correction du calcul de taille pour les liens symboliques (posteur.sh) - Support amélioré du mode symlink avec option -L pour du - Ajout .gitignore pour exclure le dossier .specstory ## Améliorations techniques - Interface utilisateur moderne avec compteur de sélection - Mise à jour visuelle en temps réel - Validation côté serveur avec gestion d'erreurs - Conservation de toutes les fonctionnalités existantes
This commit is contained in:
@@ -58,6 +58,10 @@ function updateTable(rows) {
|
||||
? ' | <a href="/autopost/dl?name=' + encodeURIComponent(row.nom) + '" class="dl-link text-blue-400 hover:underline">DL</a>'
|
||||
: '';
|
||||
|
||||
var resendLink = (parseInt(row.status) === 1)
|
||||
? ' | <a href="#" class="resend-link text-green-400 hover:underline" data-id="' + row.id + '" data-filename="' + esc(row.nom) + '">Renvoyer</a>'
|
||||
: '';
|
||||
|
||||
var tr =
|
||||
'<tr id="row-' + row.id + '" data-status="' + row.status + '" class="odd:bg-gray-800 even:bg-gray-700">' +
|
||||
'<td class="px-4 py-2 border border-gray-700 whitespace-nowrap">' + esc(row.nom) + '</td>' +
|
||||
@@ -66,7 +70,7 @@ function updateTable(rows) {
|
||||
'<td class="px-4 py-2 border border-gray-700">' +
|
||||
'<a href="#" class="edit-link text-blue-400 hover:underline" data-id="' + row.id + '" data-status="' + row.status + '">Editer</a> | ' +
|
||||
'<a href="#" class="delete-link text-blue-400 hover:underline" data-id="' + row.id + '">Supprimer</a>' +
|
||||
logLink + mediainfoLink + dlLink +
|
||||
logLink + mediainfoLink + dlLink + resendLink +
|
||||
'</td>' +
|
||||
'</tr>';
|
||||
|
||||
@@ -261,6 +265,25 @@ $(document).ready(function() {
|
||||
pendingDeleteId = null;
|
||||
}
|
||||
|
||||
// Fonctions pour la modal de renvoi
|
||||
var pendingResendId = null;
|
||||
var pendingResendFilename = null;
|
||||
|
||||
function openConfirmResendModal(id, filename) {
|
||||
pendingResendId = id;
|
||||
pendingResendFilename = filename;
|
||||
$('#confirmResendItemName').text(filename || ('ID ' + id));
|
||||
$('#confirmResendModal').removeClass('hidden').fadeIn(120);
|
||||
}
|
||||
|
||||
function closeConfirmResendModal() {
|
||||
$('#confirmResendModal').fadeOut(120, function() {
|
||||
$(this).addClass('hidden');
|
||||
});
|
||||
pendingResendId = null;
|
||||
pendingResendFilename = null;
|
||||
}
|
||||
|
||||
function showToast(msg) {
|
||||
$('#toastMsg').text(msg || 'Opération effectuée');
|
||||
$('#toast').removeClass('hidden').fadeIn(120);
|
||||
@@ -282,17 +305,33 @@ $(document).ready(function() {
|
||||
closeConfirmModal();
|
||||
});
|
||||
|
||||
// Clic sur l’overlay pour fermer
|
||||
// Clic sur l'overlay pour fermer
|
||||
$('#confirmDeleteModal').on('click', function(e) {
|
||||
if (e.target === this) { closeConfirmModal(); }
|
||||
});
|
||||
|
||||
// Gestionnaires pour la modal de renvoi
|
||||
$('#cancelResendBtn, #confirmResendClose').on('click', function() {
|
||||
closeConfirmResendModal();
|
||||
});
|
||||
|
||||
// Clic sur l'overlay pour fermer la modal de renvoi
|
||||
$('#confirmResendModal').on('click', function(e) {
|
||||
if (e.target === this) { closeConfirmResendModal(); }
|
||||
});
|
||||
|
||||
// Accessibilité clavier: Esc = fermer, Enter = confirmer
|
||||
$(document).on('keydown', function(e) {
|
||||
var modalVisible = !$('#confirmDeleteModal').hasClass('hidden');
|
||||
if (!modalVisible) return;
|
||||
if (e.key === 'Escape') { closeConfirmModal(); }
|
||||
if (e.key === 'Enter') { $('#confirmDeleteBtn').click(); }
|
||||
var deleteModalVisible = !$('#confirmDeleteModal').hasClass('hidden');
|
||||
var resendModalVisible = !$('#confirmResendModal').hasClass('hidden');
|
||||
|
||||
if (deleteModalVisible) {
|
||||
if (e.key === 'Escape') { closeConfirmModal(); }
|
||||
if (e.key === 'Enter') { $('#confirmDeleteBtn').click(); }
|
||||
} else if (resendModalVisible) {
|
||||
if (e.key === 'Escape') { closeConfirmResendModal(); }
|
||||
if (e.key === 'Enter') { $('#confirmResendBtn').click(); }
|
||||
}
|
||||
});
|
||||
|
||||
// Confirmer et supprimer via AJAX
|
||||
@@ -318,6 +357,38 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
|
||||
// Confirmer et renvoyer via AJAX
|
||||
$('#confirmResendBtn').on('click', function() {
|
||||
if (!pendingResendId) return;
|
||||
var releaseId = pendingResendId;
|
||||
var filename = pendingResendFilename;
|
||||
|
||||
var $button = $(this);
|
||||
var originalText = $button.text();
|
||||
$button.text('Envoi...');
|
||||
|
||||
$.ajax({
|
||||
url: '/autopost/resend/' + releaseId,
|
||||
type: 'POST',
|
||||
success: function(data) {
|
||||
showToast(data.message || 'Renvoi effectué avec succès');
|
||||
console.log('Renvoi réussi pour', filename);
|
||||
},
|
||||
error: function(xhr) {
|
||||
var errorMsg = 'Erreur lors du renvoi';
|
||||
if (xhr.responseJSON && xhr.responseJSON.error) {
|
||||
errorMsg = xhr.responseJSON.error;
|
||||
}
|
||||
alert(errorMsg);
|
||||
console.error('Erreur renvoi:', xhr.responseJSON);
|
||||
},
|
||||
complete: function() {
|
||||
$button.text(originalText);
|
||||
closeConfirmResendModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Affichage log
|
||||
$(document).on('click', '.log-link', function(e) {
|
||||
e.preventDefault();
|
||||
@@ -419,4 +490,219 @@ $(document).ready(function() {
|
||||
alert('Erreur lors de la copie : ' + err);
|
||||
});
|
||||
});
|
||||
|
||||
// Renvoi vers le site principal
|
||||
$(document).on('click', '.resend-link', function(e) {
|
||||
e.preventDefault();
|
||||
var releaseId = $(this).data('id');
|
||||
var filename = $(this).data('filename');
|
||||
openConfirmResendModal(releaseId, filename);
|
||||
});
|
||||
|
||||
// ==================== GESTION SÉLECTION MULTIPLE ====================
|
||||
|
||||
function updateBulkActions() {
|
||||
const selectedCheckboxes = $('.row-checkbox:checked');
|
||||
const count = selectedCheckboxes.length;
|
||||
|
||||
if (count > 0) {
|
||||
$('#bulkActions').removeClass('hidden');
|
||||
$('#selectedCount').text(count + ' élément' + (count > 1 ? 's' : '') + ' sélectionné' + (count > 1 ? 's' : ''));
|
||||
} else {
|
||||
$('#bulkActions').addClass('hidden');
|
||||
}
|
||||
|
||||
// Mettre à jour la checkbox "Tout sélectionner"
|
||||
const totalCheckboxes = $('.row-checkbox').length;
|
||||
const allChecked = count === totalCheckboxes && totalCheckboxes > 0;
|
||||
const someChecked = count > 0 && count < totalCheckboxes;
|
||||
|
||||
$('#selectAll').prop('checked', allChecked);
|
||||
$('#selectAll').prop('indeterminate', someChecked);
|
||||
}
|
||||
|
||||
// Checkbox "Tout sélectionner"
|
||||
$(document).on('click', '#selectAll', function() {
|
||||
const isChecked = $(this).is(':checked');
|
||||
$('.row-checkbox').prop('checked', isChecked);
|
||||
updateBulkActions();
|
||||
});
|
||||
|
||||
// Checkboxes individuelles
|
||||
$(document).on('click', '.row-checkbox', function() {
|
||||
updateBulkActions();
|
||||
});
|
||||
|
||||
// Bouton "Éditer la sélection"
|
||||
$(document).on('click', '#bulkEditBtn', function() {
|
||||
const selectedCheckboxes = $('.row-checkbox:checked');
|
||||
const count = selectedCheckboxes.length;
|
||||
|
||||
if (count === 0) return;
|
||||
|
||||
$('#bulkEditCount').text(count);
|
||||
$('#bulkEditModal').removeClass('hidden').fadeIn(120);
|
||||
});
|
||||
|
||||
// Bouton "Supprimer la sélection"
|
||||
$(document).on('click', '#bulkDeleteBtn', function() {
|
||||
const selectedCheckboxes = $('.row-checkbox:checked');
|
||||
const count = selectedCheckboxes.length;
|
||||
|
||||
if (count === 0) return;
|
||||
|
||||
// Construire la liste des éléments à supprimer
|
||||
let listHTML = '';
|
||||
selectedCheckboxes.each(function() {
|
||||
const name = $(this).data('name');
|
||||
const id = $(this).data('id');
|
||||
listHTML += `<div class="text-sm text-gray-300 mb-1">• ${name} (ID: ${id})</div>`;
|
||||
});
|
||||
|
||||
$('#bulkDeleteCount').text(count);
|
||||
$('#bulkDeleteList').html(listHTML);
|
||||
$('#bulkDeleteModal').removeClass('hidden').fadeIn(120);
|
||||
});
|
||||
|
||||
// Fermeture des modals en lot
|
||||
$('#bulkEditClose, #bulkDeleteClose, #cancelBulkDeleteBtn').on('click', function() {
|
||||
$('#bulkEditModal, #bulkDeleteModal').fadeOut(120, function() {
|
||||
$(this).addClass('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
// Clic sur l'overlay pour fermer les modals en lot
|
||||
$('#bulkEditModal, #bulkDeleteModal').on('click', function(e) {
|
||||
if (e.target === this) {
|
||||
$(this).fadeOut(120, function() {
|
||||
$(this).addClass('hidden');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Soumission du formulaire d'édition en lot
|
||||
$('#bulkEditForm').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const selectedCheckboxes = $('.row-checkbox:checked');
|
||||
const newStatus = $('#bulkStatusSelect').val();
|
||||
const ids = [];
|
||||
|
||||
selectedCheckboxes.each(function() {
|
||||
ids.push($(this).data('id'));
|
||||
});
|
||||
|
||||
if (ids.length === 0) return;
|
||||
|
||||
const $submitBtn = $('#bulkEditForm button[type="submit"]');
|
||||
const originalText = $submitBtn.text();
|
||||
$submitBtn.text('Mise à jour...').prop('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
url: '/autopost/bulk-edit',
|
||||
type: 'POST',
|
||||
data: {
|
||||
ids: ids,
|
||||
status: newStatus,
|
||||
_csrf: window.__BOOTSTRAP__.csrf
|
||||
},
|
||||
success: function(data) {
|
||||
// Mettre à jour l'interface pour chaque ligne modifiée
|
||||
ids.forEach(function(id) {
|
||||
updateRowStatus(id, newStatus);
|
||||
});
|
||||
|
||||
showToast(`${ids.length} élément(s) mis à jour`);
|
||||
|
||||
// Fermer la modal et désélectionner
|
||||
$('#bulkEditModal').fadeOut(120, function() {
|
||||
$(this).addClass('hidden');
|
||||
});
|
||||
$('.row-checkbox').prop('checked', false);
|
||||
updateBulkActions();
|
||||
},
|
||||
error: function(xhr) {
|
||||
let errorMsg = 'Erreur lors de la mise à jour en lot';
|
||||
if (xhr.responseJSON && xhr.responseJSON.error) {
|
||||
errorMsg = xhr.responseJSON.error;
|
||||
}
|
||||
alert(errorMsg);
|
||||
},
|
||||
complete: function() {
|
||||
$submitBtn.text(originalText).prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Confirmation de suppression en lot
|
||||
$('#confirmBulkDeleteBtn').on('click', function() {
|
||||
const selectedCheckboxes = $('.row-checkbox:checked');
|
||||
const ids = [];
|
||||
|
||||
selectedCheckboxes.each(function() {
|
||||
ids.push($(this).data('id'));
|
||||
});
|
||||
|
||||
if (ids.length === 0) return;
|
||||
|
||||
const $button = $(this);
|
||||
const originalText = $button.text();
|
||||
$button.text('Suppression...').prop('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
url: '/autopost/bulk-delete',
|
||||
type: 'POST',
|
||||
data: {
|
||||
ids: ids,
|
||||
_csrf: window.__BOOTSTRAP__.csrf
|
||||
},
|
||||
success: function(data) {
|
||||
// Supprimer visuellement les lignes
|
||||
ids.forEach(function(id) {
|
||||
$('#row-' + id)
|
||||
.css('outline', '2px solid rgba(239,68,68,0.6)')
|
||||
.fadeOut('300', function(){ $(this).remove(); });
|
||||
});
|
||||
|
||||
showToast(`${ids.length} élément(s) supprimé(s)`);
|
||||
|
||||
// Fermer la modal
|
||||
$('#bulkDeleteModal').fadeOut(120, function() {
|
||||
$(this).addClass('hidden');
|
||||
});
|
||||
updateBulkActions();
|
||||
},
|
||||
error: function(xhr) {
|
||||
let errorMsg = 'Erreur lors de la suppression en lot';
|
||||
if (xhr.responseJSON && xhr.responseJSON.error) {
|
||||
errorMsg = xhr.responseJSON.error;
|
||||
}
|
||||
alert(errorMsg);
|
||||
},
|
||||
complete: function() {
|
||||
$button.text(originalText).prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Fonction utilitaire pour mettre à jour le statut d'une ligne
|
||||
function updateRowStatus(id, newStatus) {
|
||||
const $row = $('#row-' + id);
|
||||
const $statusCell = $row.find('.status-text');
|
||||
|
||||
let statusText = '';
|
||||
let statusClass = '';
|
||||
switch (parseInt(newStatus)) {
|
||||
case 0: statusText = 'EN ATTENTE'; statusClass = 'bg-cyan-500 text-black font-bold'; break;
|
||||
case 1: statusText = 'ENVOI TERMINÉ'; statusClass = 'bg-green-300 text-black font-bold'; break;
|
||||
case 2: statusText = 'ERREUR'; statusClass = 'bg-red-300 text-black font-bold'; break;
|
||||
case 3: statusText = 'DEJA DISPONIBLE'; statusClass = 'bg-pink-300 text-black font-bold'; break;
|
||||
case 4: statusText = 'EN COURS'; statusClass = 'bg-yellow-300 text-black font-bold'; break;
|
||||
default: statusText = 'INCONNU';
|
||||
}
|
||||
|
||||
$statusCell.removeClass().addClass('px-4 py-2 border border-gray-700 status-text whitespace-nowrap ' + statusClass);
|
||||
$statusCell.text(statusText);
|
||||
$row.attr('data-status', newStatus);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user