feat(front) now can block

This commit is contained in:
2026-05-30 00:31:21 -06:00
parent 7ec78cb76e
commit d769aab9e4
+154 -13
View File
@@ -130,7 +130,11 @@
</div> </div>
<div class="bg-zinc-900 border border-zinc-800 rounded-2xl p-4"> <div class="bg-zinc-900 border border-zinc-800 rounded-2xl p-4">
<h3 class="font-bold text-lg mb-3">Lista de Sesiones</h3> <div class="flex justify-between items-center mb-3">
<h3 class="font-bold text-lg">Lista de Sesiones</h3>
</div>
<input id="sessionSearch" placeholder="Buscar por teléfono, nombre o resumen..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl p-3 outline-none focus:ring-2 focus:ring-indigo-500 mb-3"/>
<div id="sessionList" class="space-y-3"></div> <div id="sessionList" class="space-y-3"></div>
</div> </div>
</div> </div>
@@ -250,6 +254,25 @@
</div> </div>
</div> </div>
<!-- ========================= -->
<!-- BLOCK SESSION MODAL -->
<!-- ========================= -->
<div id="blockSessionModal" class="hidden fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
<div class="bg-zinc-900 border border-zinc-800 rounded-2xl max-w-sm w-full p-6 shadow-2xl text-center">
<h2 class="text-xl font-bold mb-3">¿Bloquear Sesión?</h2>
<p class="text-sm text-zinc-400 mb-6">¿Estás seguro de que quieres bloquear esta sesión? El número de teléfono será bloqueado.</p>
<input type="hidden" id="blockSessionId" />
<input type="hidden" id="blockSessionPhone" />
<div class="flex gap-3">
<button id="confirmBlockSessionBtn" class="flex-1 bg-red-600 hover:bg-red-500 transition rounded-xl p-3 font-semibold text-white">Bloquear</button>
<button id="cancelBlockSessionBtn" class="flex-1 bg-zinc-800 hover:bg-zinc-700 transition rounded-xl p-3 font-semibold text-white">Cancelar</button>
</div>
<div id="blockSessionMsg" class="text-sm mt-3"></div>
</div>
</div>
<script> <script>
// ============================= // =============================
// CONFIG // CONFIG
@@ -564,6 +587,8 @@
// sessions table must exist // sessions table must exist
// columns: id, campaign_id, created_at, ip, user_agent, etc // columns: id, campaign_id, created_at, ip, user_agent, etc
// ============================= // =============================
let allSessions = [];
async function loadSessions() { async function loadSessions() {
const campaignFilter = document.getElementById("sessionsCampaignFilter").value; const campaignFilter = document.getElementById("sessionsCampaignFilter").value;
@@ -579,26 +604,43 @@
const { data, error } = await query; const { data, error } = await query;
if (error) {
document.getElementById("sessionList").innerHTML = `<div class="text-red-400 text-sm">Error loading sessions: ${error.message}</div>`;
return;
}
allSessions = data || [];
renderSessions();
}
function renderSessions() {
const searchTerm = document.getElementById("sessionSearch").value.toLowerCase();
const list = document.getElementById("sessionList"); const list = document.getElementById("sessionList");
list.innerHTML = ""; list.innerHTML = "";
if (error) { let filtered = allSessions;
list.innerHTML = `<div class="text-red-400 text-sm">Error loading sessions: ${error.message}</div>`; if (searchTerm) {
filtered = allSessions.filter(s =>
(s.phone && s.phone.toLowerCase().includes(searchTerm)) ||
(s.name && s.name.toLowerCase().includes(searchTerm)) ||
(s.summary && s.summary.toLowerCase().includes(searchTerm))
);
}
if (filtered.length === 0) {
list.innerHTML = `<div class="text-zinc-400 text-sm">${allSessions.length === 0 ? "No sessions found." : "No sessions match the search."}</div>`;
return; return;
} }
if (!data || data.length === 0) { filtered.forEach(s => {
list.innerHTML = `<div class="text-zinc-400 text-sm">No sessions found.</div>`;
return;
}
data.forEach(s => {
const card = document.createElement("div"); const card = document.createElement("div");
card.className = "bg-zinc-950 border border-zinc-800 rounded-2xl p-4"; card.className = "bg-zinc-950 border border-zinc-800 rounded-2xl p-4";
const statusClass = s.finished ? "bg-green-500/20 text-green-500" : "bg-amber-500/20 text-amber-500"; const statusClass = s.finished ? "bg-green-500/20 text-green-500" : "bg-amber-500/20 text-amber-500";
const statusText = s.finished ? "Finished" : "Active"; const statusText = s.finished ? "Finished" : "Active";
const blockedBadge = s.block ? `<span class="text-[10px] px-2 py-0.5 rounded-full font-bold uppercase bg-red-500/20 text-red-500">Blocked</span>` : '';
card.innerHTML = ` card.innerHTML = `
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
<div class="w-full"> <div class="w-full">
@@ -610,9 +652,22 @@
<div class="text-[10px] px-2 py-0.5 rounded-full font-bold uppercase ${statusClass}"> <div class="text-[10px] px-2 py-0.5 rounded-full font-bold uppercase ${statusClass}">
${statusText} ${statusText}
</div> </div>
<button class="deleteSessionBtn bg-red-500/20 hover:bg-red-500/40 text-red-400 p-1.5 rounded-lg transition" data-id="${s.id}" data-phone="${s.phone}"> ${blockedBadge}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-3.5 h-3.5"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.134-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.067-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /></svg> <div class="relative">
<button class="sessionMenuBtn bg-zinc-800 hover:bg-zinc-700 p-1.5 rounded-lg transition" data-id="${s.id}" data-phone="${s.phone}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 12.75a.75.75 0 110-1.5.75.75 0 010 1.5zM12 18.75a.75.75 0 110-1.5.75.75 0 010 1.5z" />
</svg>
</button> </button>
<div class="sessionMenu hidden absolute right-0 top-full mt-1 bg-zinc-900 border border-zinc-800 rounded-xl shadow-xl z-10 min-w-[140px] overflow-hidden">
<button class="blockSessionItem w-full text-left px-4 py-2.5 text-sm hover:bg-zinc-800 transition text-red-400" data-id="${s.id}" data-phone="${s.phone}">
${s.block ? 'Desbloquear' : 'Bloquear'}
</button>
<button class="deleteSessionItem w-full text-left px-4 py-2.5 text-sm hover:bg-zinc-800 transition text-red-400 border-t border-zinc-800" data-id="${s.id}" data-phone="${s.phone}">
Eliminar
</button>
</div>
</div>
</div> </div>
</div> </div>
<div class="text-xs text-zinc-400 mt-2"><span class="font-semibold text-zinc-300">Summary:</span> ${s.summary || "No summary yet."}</div> <div class="text-xs text-zinc-400 mt-2"><span class="font-semibold text-zinc-300">Summary:</span> ${s.summary || "No summary yet."}</div>
@@ -629,17 +684,51 @@
list.appendChild(card); list.appendChild(card);
}); });
list.querySelectorAll(".deleteSessionBtn").forEach(btn => { list.querySelectorAll(".sessionMenuBtn").forEach(btn => {
btn.addEventListener("click", () => { btn.addEventListener("click", (e) => {
e.stopPropagation();
const menu = btn.nextElementSibling;
const wasHidden = menu.classList.contains("hidden");
document.querySelectorAll(".sessionMenu").forEach(m => m.classList.add("hidden"));
if (wasHidden) menu.classList.remove("hidden");
});
});
list.querySelectorAll(".deleteSessionItem").forEach(btn => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const phone = btn.dataset.phone; const phone = btn.dataset.phone;
const id = btn.dataset.id; const id = btn.dataset.id;
if (id && phone && phone !== 'unknown') { if (id && phone && phone !== 'unknown') {
deleteSession(id, phone); deleteSession(id, phone);
} }
document.querySelectorAll(".sessionMenu").forEach(m => m.classList.add("hidden"));
});
});
list.querySelectorAll(".blockSessionItem").forEach(btn => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const phone = btn.dataset.phone;
const id = btn.dataset.id;
if (id && phone && phone !== 'unknown') {
if (btn.textContent.trim() === 'Desbloquear') {
unblockSession(id, phone);
} else {
openBlockModal(id, phone);
}
}
document.querySelectorAll(".sessionMenu").forEach(m => m.classList.add("hidden"));
}); });
}); });
} }
document.addEventListener("click", () => {
document.querySelectorAll(".sessionMenu").forEach(m => m.classList.add("hidden"));
});
document.getElementById("sessionSearch").addEventListener("input", renderSessions);
async function deleteSession(id, phone) { async function deleteSession(id, phone) {
if (!confirm(`Delete session for ${phone} and all its chat history?`)) return; if (!confirm(`Delete session for ${phone} and all its chat history?`)) return;
@@ -666,6 +755,55 @@
await loadSessions(); await loadSessions();
} }
function openBlockModal(id, phone) {
document.getElementById("blockSessionId").value = id;
document.getElementById("blockSessionPhone").value = phone;
setMessage(document.getElementById("blockSessionMsg"), "");
show(document.getElementById("blockSessionModal"));
}
function closeBlockModal() {
hide(document.getElementById("blockSessionModal"));
}
async function confirmBlockSession() {
const id = document.getElementById("blockSessionId").value;
const phone = document.getElementById("blockSessionPhone").value;
const msg = document.getElementById("blockSessionMsg");
setMessage(msg, "Bloqueando...");
const { error } = await supabaseClient
.from("sessions")
.update({ block: true })
.eq("phone", phone);
if (error) {
setMessage(msg, "Error: " + error.message, true);
return;
}
setMessage(msg, "Sesión bloqueada!", false);
closeBlockModal();
await loadSessions();
}
async function unblockSession(id, phone) {
if (!confirm(`Desbloquear sesión para ${phone}?`)) return;
const { error } = await supabaseClient
.from("sessions")
.update({ block: false })
.eq("phone", phone);
if (error) {
alert("Error: " + error.message);
return;
}
await loadSessions();
}
// ============================= // =============================
// MEDIA UPLOAD // MEDIA UPLOAD
// media table must exist // media table must exist
@@ -894,6 +1032,9 @@
document.getElementById("cancelDeleteMediaBtn").addEventListener("click", closeDeleteMediaModal); document.getElementById("cancelDeleteMediaBtn").addEventListener("click", closeDeleteMediaModal);
document.getElementById("confirmDeleteMediaBtn").addEventListener("click", confirmDeleteMedia); document.getElementById("confirmDeleteMediaBtn").addEventListener("click", confirmDeleteMedia);
document.getElementById("cancelBlockSessionBtn").addEventListener("click", closeBlockModal);
document.getElementById("confirmBlockSessionBtn").addEventListener("click", confirmBlockSession);
setupTabs(); setupTabs();
// Keep session state updated // Keep session state updated