up
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
if (!isset($_SESSION['admin_logged_in']) || $_SESSION['admin_logged_in'] !== true) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>📅 Mensajes Programados - WhatsApp Bot</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.scheduled-message-card {
|
||||
margin-bottom: 15px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.scheduled-message-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 0.85rem;
|
||||
padding: 5px 12px;
|
||||
}
|
||||
|
||||
.status-pending { background: #ffc107; color: #000; }
|
||||
.status-sent { background: #28a745; color: #fff; }
|
||||
.status-failed { background: #dc3545; color: #fff; }
|
||||
.status-cancelled { background: #6c757d; color: #fff; }
|
||||
|
||||
.message-preview {
|
||||
background: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-top: 10px;
|
||||
font-size: 0.9rem;
|
||||
max-height: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="text-white">
|
||||
<i class="fas fa-calendar-alt"></i> Mensajes Programados
|
||||
</h1>
|
||||
<a href="index.php" class="btn btn-light">
|
||||
<i class="fas fa-arrow-left"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Filtros -->
|
||||
<div class="filter-section">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Cliente</label>
|
||||
<select class="form-select" id="filter-user">
|
||||
<option value="">Todos los clientes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Estado</label>
|
||||
<select class="form-select" id="filter-status">
|
||||
<option value="">Todos</option>
|
||||
<option value="pending" selected>Pendientes</option>
|
||||
<option value="sent">Enviados</option>
|
||||
<option value="failed">Fallidos</option>
|
||||
<option value="cancelled">Cancelados</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Desde</label>
|
||||
<input type="date" class="form-control" id="filter-date-from">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Hasta</label>
|
||||
<input type="date" class="form-control" id="filter-date-to">
|
||||
</div>
|
||||
<div class="col-md-1">
|
||||
<label class="form-label"> </label>
|
||||
<button class="btn btn-primary w-100" onclick="loadScheduledMessages()">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lista de mensajes -->
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-list"></i> Mensajes Programados
|
||||
</h5>
|
||||
<span class="badge bg-light text-dark" id="total-count">0 mensajes</span>
|
||||
</div>
|
||||
<div class="card-body" id="messages-container">
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-spinner fa-spin fa-3x text-muted"></i>
|
||||
<p class="mt-3 text-muted">Cargando mensajes...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal para editar mensaje -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-edit"></i> Editar Mensaje Programado
|
||||
</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="edit-message-id">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Fecha</label>
|
||||
<input type="date" class="form-control" id="edit-date">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Hora</label>
|
||||
<input type="time" class="form-control" id="edit-time">
|
||||
</div>
|
||||
|
||||
<div id="edit-parameters-container"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
Cancelar
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveEdit()">
|
||||
<i class="fas fa-save"></i> Guardar Cambios
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
let currentMessages = [];
|
||||
|
||||
// Cargar usuarios para el filtro
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const response = await fetch('api/get_users.php');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
const select = document.getElementById('filter-user');
|
||||
data.data.forEach(user => {
|
||||
const option = document.createElement('option');
|
||||
option.value = user.id;
|
||||
option.textContent = `${user.name} (${user.phone_number})`;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading users:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Cargar mensajes programados
|
||||
async function loadScheduledMessages() {
|
||||
const container = document.getElementById('messages-container');
|
||||
container.innerHTML = '<div class="text-center py-5"><i class="fas fa-spinner fa-spin fa-3x text-muted"></i></div>';
|
||||
|
||||
try {
|
||||
const userId = document.getElementById('filter-user').value;
|
||||
const status = document.getElementById('filter-status').value;
|
||||
const dateFrom = document.getElementById('filter-date-from').value;
|
||||
const dateTo = document.getElementById('filter-date-to').value;
|
||||
|
||||
let url = 'api/get_scheduled_messages.php?limit=100';
|
||||
if (userId) url += `&user_id=${userId}`;
|
||||
if (status) url += `&status=${status}`;
|
||||
if (dateFrom) url += `&date_from=${dateFrom}`;
|
||||
if (dateTo) url += `&date_to=${dateTo}`;
|
||||
|
||||
console.log('Fetching:', url);
|
||||
const response = await fetch(url);
|
||||
console.log('Response status:', response.status);
|
||||
const data = await response.json();
|
||||
console.log('Response data:', data);
|
||||
|
||||
if (data.success) {
|
||||
currentMessages = data.data;
|
||||
renderMessages(data.data);
|
||||
document.getElementById('total-count').textContent = `${data.total} mensaje${data.total !== 1 ? 's' : ''}`;
|
||||
} else {
|
||||
container.innerHTML = `<div class="alert alert-danger">Error: ${data.error || 'Error desconocido'}</div>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error completo:', error);
|
||||
container.innerHTML = `<div class="alert alert-danger">Error de conexión: ${error.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Renderizar mensajes
|
||||
function renderMessages(messages) {
|
||||
const container = document.getElementById('messages-container');
|
||||
|
||||
if (messages.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-calendar-times fa-3x text-muted"></i>
|
||||
<p class="mt-3 text-muted">No hay mensajes programados</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="row">';
|
||||
|
||||
messages.forEach(msg => {
|
||||
const statusClass = `status-${msg.status}`;
|
||||
const date = new Date(msg.scheduled_date + ' ' + msg.scheduled_time);
|
||||
const dateStr = date.toLocaleDateString('es-ES', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
const timeStr = date.toLocaleTimeString('es-ES', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
let preview = '';
|
||||
if (msg.message_type === 'template') {
|
||||
preview = `<strong>Plantilla:</strong> ${msg.template_display_name || msg.template_name}`;
|
||||
if (msg.template_body) {
|
||||
let bodyPreview = msg.template_body;
|
||||
if (msg.template_parameters && Array.isArray(msg.template_parameters)) {
|
||||
msg.template_parameters.forEach((param, index) => {
|
||||
bodyPreview = bodyPreview.replace(`{{${index + 1}}}`, param);
|
||||
});
|
||||
}
|
||||
preview += `<div class="message-preview">${bodyPreview}</div>`;
|
||||
}
|
||||
} else {
|
||||
preview = `<div class="message-preview">${msg.message_content}</div>`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<div class="col-md-6">
|
||||
<div class="card scheduled-message-card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-user"></i> ${msg.user_name}
|
||||
<small class="text-muted">(${msg.phone_number})</small>
|
||||
</h6>
|
||||
<span class="badge ${statusClass}">${getStatusText(msg.status)}</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<i class="fas fa-calendar"></i> ${dateStr}
|
||||
<br>
|
||||
<i class="fas fa-clock"></i> ${timeStr}
|
||||
</div>
|
||||
|
||||
${preview}
|
||||
|
||||
${msg.error_message ? `<div class="alert alert-danger mt-2 mb-0"><small>${msg.error_message}</small></div>` : ''}
|
||||
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
${msg.status === 'pending' ? `
|
||||
<button class="btn btn-sm btn-primary" onclick="editMessage(${msg.id})">
|
||||
<i class="fas fa-edit"></i> Editar
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning" onclick="cancelMessage(${msg.id})">
|
||||
<i class="fas fa-ban"></i> Cancelar
|
||||
</button>
|
||||
` : ''}
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteMessage(${msg.id})">
|
||||
<i class="fas fa-trash"></i> Eliminar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function getStatusText(status) {
|
||||
const texts = {
|
||||
'pending': 'Pendiente',
|
||||
'sent': 'Enviado',
|
||||
'failed': 'Fallido',
|
||||
'cancelled': 'Cancelado'
|
||||
};
|
||||
return texts[status] || status;
|
||||
}
|
||||
|
||||
// Editar mensaje
|
||||
function editMessage(id) {
|
||||
const msg = currentMessages.find(m => m.id == id);
|
||||
if (!msg) return;
|
||||
|
||||
document.getElementById('edit-message-id').value = msg.id;
|
||||
document.getElementById('edit-date').value = msg.scheduled_date;
|
||||
document.getElementById('edit-time').value = msg.scheduled_time;
|
||||
|
||||
// Si tiene parámetros de plantilla, mostrar campos para editarlos
|
||||
const paramsContainer = document.getElementById('edit-parameters-container');
|
||||
paramsContainer.innerHTML = '';
|
||||
|
||||
if (msg.template_parameters && Array.isArray(msg.template_parameters)) {
|
||||
paramsContainer.innerHTML = '<label class="form-label">Parámetros de la Plantilla</label>';
|
||||
msg.template_parameters.forEach((param, index) => {
|
||||
paramsContainer.innerHTML += `
|
||||
<div class="mb-2">
|
||||
<input type="text"
|
||||
class="form-control form-control-sm"
|
||||
id="edit-param-${index}"
|
||||
value="${param}"
|
||||
placeholder="Variable ${index + 1}">
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('editModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
// Guardar cambios
|
||||
async function saveEdit() {
|
||||
const id = document.getElementById('edit-message-id').value;
|
||||
const date = document.getElementById('edit-date').value;
|
||||
const time = document.getElementById('edit-time').value;
|
||||
|
||||
// Recopilar parámetros editados
|
||||
const msg = currentMessages.find(m => m.id == id);
|
||||
let parameters = null;
|
||||
|
||||
if (msg && msg.template_parameters && Array.isArray(msg.template_parameters)) {
|
||||
parameters = [];
|
||||
msg.template_parameters.forEach((_, index) => {
|
||||
const input = document.getElementById(`edit-param-${index}`);
|
||||
if (input) {
|
||||
parameters.push(input.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
id: parseInt(id),
|
||||
scheduled_date: date,
|
||||
scheduled_time: time
|
||||
};
|
||||
|
||||
if (parameters) {
|
||||
payload.template_parameters = parameters;
|
||||
}
|
||||
|
||||
const response = await fetch('api/update_scheduled_message.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('Mensaje actualizado exitosamente');
|
||||
bootstrap.Modal.getInstance(document.getElementById('editModal')).hide();
|
||||
loadScheduledMessages();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Error al guardar cambios');
|
||||
}
|
||||
}
|
||||
|
||||
// Cancelar mensaje
|
||||
async function cancelMessage(id) {
|
||||
if (!confirm('¿Cancelar este mensaje programado?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('api/update_scheduled_message.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, status: 'cancelled' })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
loadScheduledMessages();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Error al cancelar mensaje');
|
||||
}
|
||||
}
|
||||
|
||||
// Eliminar mensaje
|
||||
async function deleteMessage(id) {
|
||||
if (!confirm('¿Eliminar este mensaje programado? Esta acción no se puede deshacer.')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('api/delete_scheduled_message.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
loadScheduledMessages();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Error al eliminar mensaje');
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadUsers();
|
||||
loadScheduledMessages();
|
||||
|
||||
// Establecer fecha de hoy como mínimo
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('filter-date-from').value = today;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user