Files
soft_usite/resources/views/renovaciones/historial.html
T
2026-04-29 23:36:30 -05:00

144 lines
7.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<div x-data="app" class="bg-white rounded-lg shadow">
<div x-show="loading" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex justify-center items-center z-50">
<img src="../img/loading.gif" alt="Cargando..." class="w-16 h-16" />
</div>
<div class="container mx-auto p-6 w-full">
<div class="justify-between items-center w-full md:flex mb-4">
<div>
<h1 class="text-2xl font-bold mb-1">Historial de Notificaciones</h1>
<p class="text-sm text-gray-500">Registro de correos enviados por el sistema</p>
</div>
</div>
<div class="overflow-x-auto">
<table class="table-auto w-full text-sm">
<thead class="text-left border-b border-gray-200 bg-gray-50">
<tr>
<th class="py-2 px-3">Cliente</th>
<th class="py-2 px-3">Regla</th>
<th class="py-2 px-3">Asunto</th>
<th class="py-2 px-3">Fecha</th>
<th class="py-2 px-3">Estado</th>
<th class="py-2 px-3 w-28"></th>
</tr>
</thead>
<tbody class="text-gray-600">
<template x-for="d in datos" :key="d.ID">
<tr class="hover:bg-gray-50 border-b border-gray-100">
<td class="py-2 px-3">
<div class="font-medium" x-text="d.cliente?.nombre||'—'"></div>
<div class="text-xs text-gray-400" x-text="d.cliente?.email||''"></div>
</td>
<td class="py-2 px-3 text-gray-500 text-xs" x-text="d.regla?.nombre||'—'"></td>
<td class="py-2 px-3 max-w-xs truncate" x-text="d.asunto"></td>
<td class="py-2 px-3 text-xs" x-text="fmtDate(d.fecha_envio)"></td>
<td class="py-2 px-3">
<span class="px-2 py-0.5 rounded text-xs font-medium"
:class="{
'bg-green-100 text-green-700': d.estado==='enviado',
'bg-red-100 text-red-700': d.estado==='fallido',
'bg-yellow-100 text-yellow-700': d.estado==='pendiente'
}"
x-text="d.estado"></span>
</td>
<td class="py-2 px-3">
<div class="flex gap-2">
<button @click="verPreview(d)" title="Ver correo" class="text-gray-400 hover:text-blue-500">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/></svg>
</button>
<button @click="reenviar(d)" title="Reenviar" class="text-gray-400 hover:text-purple-500">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5"/></svg>
</button>
</div>
</td>
</tr>
</template>
<tr x-show="!loading && datos.length===0">
<td colspan="6" class="text-center text-gray-400 py-8">Sin registros</td>
</tr>
</tbody>
</table>
</div>
<div class="flex items-center justify-between mt-4 text-sm text-gray-500">
<span>Total: <span x-text="total"></span></span>
<div class="flex gap-1">
<button @click="prevPage()" :disabled="page===1" class="px-3 py-1 rounded border disabled:opacity-40"></button>
<span class="px-3 py-1" x-text="'Pág. '+page+' / '+totalPages"></span>
<button @click="nextPage()" :disabled="page>=totalPages" class="px-3 py-1 rounded border disabled:opacity-40"></button>
</div>
</div>
</div>
<!-- Modal preview -->
<div x-show="previewModal" x-cloak class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl mx-4 p-5 max-h-[90vh] overflow-y-auto" @click.stop>
<div class="flex justify-between items-center mb-3">
<div>
<h2 class="text-lg font-semibold">Vista del correo</h2>
<p class="text-xs text-gray-400" x-text="previewAsunto"></p>
</div>
<button @click="previewModal=false" class="text-gray-400 hover:text-gray-700"></button>
</div>
<iframe x-ref="previewFrame" class="w-full border rounded" style="height:500px"></iframe>
</div>
</div>
<div x-show="toast.show" x-cloak x-transition
class="fixed bottom-4 right-4 z-[100] px-4 py-3 rounded shadow-lg text-sm text-white"
:class="toast.type==='error' ? 'bg-red-500' : 'bg-[#8eb02f]'"
x-text="toast.msg"></div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('app', () => ({
loading: false,
datos: [], total: 0, totalPages: 1, page: 1, limit: 20,
previewModal: false, previewAsunto: '',
toast: { show:false, msg:'', type:'ok' },
async init() { await this.loadData(); },
async loadData() {
this.loading = true;
const { data } = await axios.get(`/app/api/historial-notificaciones?page=${this.page}&limit=${this.limit}`);
this.datos = data.registros || [];
this.total = data.total || 0;
this.totalPages = data.totalPages || 1;
this.loading = false;
},
async verPreview(d) {
const { data } = await axios.get(`/app/api/historial-notificaciones/${d.ID}/preview`);
this.previewAsunto = data.asunto || '';
this.previewModal = true;
this.$nextTick(() => {
this.$refs.previewFrame.srcdoc = data.html || '<p style="padding:1rem;color:#999">Sin contenido guardado</p>';
});
},
async reenviar(d) {
if (!confirm(`¿Reenviar correo a ${d.cliente?.email}?`)) return;
try {
await axios.post(`/app/api/historial-notificaciones/${d.ID}/reenviar`);
this.showToast('Reenviado correctamente');
await this.loadData();
} catch(e) { this.showToast(e.response?.data?.error||'Error', 'error'); }
},
fmtDate(raw) {
if (!raw) return '—';
return new Date(raw).toLocaleString('es-ES', { dateStyle:'short', timeStyle:'short' });
},
prevPage() { if(this.page>1){ this.page--; this.loadData(); } },
nextPage() { if(this.page<this.totalPages){ this.page++; this.loadData(); } },
showToast(msg, type='ok') {
this.toast = { show:true, msg, type };
setTimeout(() => this.toast.show = false, 3000);
}
}));
});
</script>