improved dashboard campaing and session tab, also mod supabase url
This commit is contained in:
+175
-53
@@ -86,7 +86,7 @@
|
||||
<input id="newCampaignName" placeholder="Campaign name"
|
||||
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl p-3 outline-none"/>
|
||||
|
||||
<textarea id="newCampaignNotes" placeholder="Notes (optional)"
|
||||
<textarea id="newCampaignNotes" placeholder="AI Prompt / Instructions (optional)"
|
||||
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl p-3 outline-none"></textarea>
|
||||
|
||||
<button id="createCampaignBtn"
|
||||
@@ -99,7 +99,12 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-zinc-900 border border-zinc-800 rounded-2xl p-4">
|
||||
<h3 class="font-bold text-lg mb-3">Campaign List</h3>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h3 class="font-bold text-lg">Campaign List</h3>
|
||||
<button id="toggleInactiveBtn" class="text-xs bg-zinc-800 hover:bg-zinc-700 px-3 py-1.5 rounded-lg border border-zinc-700 transition">
|
||||
Show Inactive
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="campaignList" class="space-y-3"></div>
|
||||
</div>
|
||||
@@ -168,11 +173,58 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ========================= -->
|
||||
<!-- EDIT CAMPAIGN MODAL -->
|
||||
<!-- ========================= -->
|
||||
<div id="editCampaignModal" 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-lg w-full p-6 shadow-2xl">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Edit Campaign</h2>
|
||||
<button id="closeModalBtn" class="text-zinc-500 hover:text-white transition text-2xl">×</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<input type="hidden" id="editCampaignId" />
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-zinc-400 mb-1">Name</label>
|
||||
<input id="editCampaignName" class="w-full bg-zinc-950 border border-zinc-800 rounded-xl p-3 outline-none focus:ring-2 focus:ring-indigo-500"/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-zinc-400 mb-1">Keywords (comma separated)</label>
|
||||
<input id="editCampaignKeywords" class="w-full bg-zinc-950 border border-zinc-800 rounded-xl p-3 outline-none focus:ring-2 focus:ring-indigo-500" placeholder="e.g. house, pool, garden"/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-zinc-400 mb-1">AI Prompt</label>
|
||||
<textarea id="editCampaignPrompt" class="w-full bg-zinc-950 border border-zinc-800 rounded-xl p-3 outline-none h-24 focus:ring-2 focus:ring-indigo-500" placeholder="Instructions for AI..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox" id="editCampaignWelcomePhotos" class="w-4 h-4 rounded border-zinc-800 bg-zinc-950 text-indigo-600 focus:ring-indigo-500"/>
|
||||
<label for="editCampaignWelcomePhotos" class="text-sm font-semibold text-zinc-300">Require Welcome Photos</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="checkbox" id="editCampaignActive" class="w-4 h-4 rounded border-zinc-800 bg-zinc-950 text-indigo-600 focus:ring-indigo-500"/>
|
||||
<label for="editCampaignActive" class="text-sm font-semibold text-zinc-300">Campaign Active</label>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex gap-3">
|
||||
<button id="saveCampaignBtn" class="flex-1 bg-indigo-600 hover:bg-indigo-500 transition rounded-xl p-3 font-semibold text-white">Save Changes</button>
|
||||
<button id="cancelModalBtn" class="flex-1 bg-zinc-800 hover:bg-zinc-700 transition rounded-xl p-3 font-semibold text-white">Cancel</button>
|
||||
</div>
|
||||
<div id="editModalMsg" class="text-sm text-center"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// =============================
|
||||
// CONFIG
|
||||
// =============================
|
||||
const SUPABASE_URL = "http://144.217.160.51:8000";
|
||||
const SUPABASE_URL = "https://rehavit.beroth.moe/supabase";
|
||||
const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzc2NTU4OTc2LCJleHAiOjE5MzQyMzg5NzZ9.eFvvIQUstht8TxNffqAcqfmgS7W2JMZsDxkV41XBPOA";
|
||||
|
||||
// This bucket must exist in Supabase Storage
|
||||
@@ -180,6 +232,9 @@
|
||||
|
||||
const supabaseClient = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
||||
|
||||
let allCampaigns = [];
|
||||
let showInactive = false;
|
||||
|
||||
// =============================
|
||||
// UI HELPERS
|
||||
// =============================
|
||||
@@ -272,13 +327,8 @@
|
||||
|
||||
// =============================
|
||||
// CAMPAIGNS CRUD
|
||||
// campaigns table must exist
|
||||
// columns: id (uuid), name (text), notes (text), created_at
|
||||
// =============================
|
||||
async function loadCampaigns() {
|
||||
const list = document.getElementById("campaignList");
|
||||
list.innerHTML = "";
|
||||
|
||||
const { data, error } = await supabaseClient
|
||||
.from("campaigns")
|
||||
.select("*")
|
||||
@@ -289,6 +339,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
allCampaigns = data || [];
|
||||
|
||||
// Populate filters
|
||||
const sessionsFilter = document.getElementById("sessionsCampaignFilter");
|
||||
const mediaSelect = document.getElementById("mediaCampaignSelect");
|
||||
@@ -296,7 +348,7 @@
|
||||
sessionsFilter.innerHTML = `<option value="">All campaigns</option>`;
|
||||
mediaSelect.innerHTML = `<option value="">Select campaign</option>`;
|
||||
|
||||
data.forEach(c => {
|
||||
allCampaigns.forEach(c => {
|
||||
const opt1 = document.createElement("option");
|
||||
opt1.value = c.id;
|
||||
opt1.innerText = c.name;
|
||||
@@ -308,25 +360,42 @@
|
||||
mediaSelect.appendChild(opt2);
|
||||
});
|
||||
|
||||
data.forEach(c => {
|
||||
renderCampaigns();
|
||||
}
|
||||
|
||||
function renderCampaigns() {
|
||||
const list = document.getElementById("campaignList");
|
||||
list.innerHTML = "";
|
||||
|
||||
const filtered = showInactive ? allCampaigns : allCampaigns.filter(c => c.active !== false);
|
||||
|
||||
filtered.forEach(c => {
|
||||
const card = document.createElement("div");
|
||||
card.className = "bg-zinc-950 border border-zinc-800 rounded-2xl p-4";
|
||||
|
||||
const statusColor = c.active === false ? "bg-red-500/20 text-red-500" : "bg-green-500/20 text-green-500";
|
||||
const statusText = c.active === false ? "Inactive" : "Active";
|
||||
const keywordsStr = Array.isArray(c.keywords) ? c.keywords.join(", ") : "";
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="flex justify-between items-start gap-3">
|
||||
<div>
|
||||
<div class="font-bold text-lg">${c.name}</div>
|
||||
<div class="text-xs text-zinc-400 mt-1">${c.notes || ""}</div>
|
||||
<div class="text-xs text-zinc-500 mt-2">Created: ${formatDate(c.created_at)}</div>
|
||||
<div class="text-xs text-zinc-500 mt-1">ID: ${c.id}</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="font-bold text-lg">${c.name}</div>
|
||||
<span class="text-[10px] px-2 py-0.5 rounded-full font-bold uppercase ${statusColor}">${statusText}</span>
|
||||
</div>
|
||||
${c.send_welcome_photos ? `<div class="text-xs text-indigo-400 font-semibold mt-1">📸 Requires Welcome Photos</div>` : ''}
|
||||
${keywordsStr ? `<div class="text-xs text-zinc-400 mt-2"><span class="font-semibold text-zinc-300">Keywords:</span> ${keywordsStr}</div>` : ''}
|
||||
${c.promt ? `<div class="text-xs text-zinc-400 mt-1 line-clamp-2"><span class="font-semibold text-zinc-300">Prompt:</span> ${c.promt}</div>` : ''}
|
||||
<div class="text-xs text-zinc-600 mt-3">Created: ${formatDate(c.created_at)} • ID: ${c.id.split('-')[0]}...</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<button class="editBtn bg-zinc-800 hover:bg-zinc-700 text-xs px-3 py-2 rounded-xl"
|
||||
data-id="${c.id}" data-name="${c.name}" data-notes="${c.notes || ""}">
|
||||
<div class="flex flex-col gap-2 shrink-0">
|
||||
<button class="editBtn bg-zinc-800 hover:bg-zinc-700 text-xs px-3 py-2 rounded-xl transition"
|
||||
data-id="${c.id}">
|
||||
Edit
|
||||
</button>
|
||||
<button class="deleteBtn bg-red-600 hover:bg-red-500 text-xs px-3 py-2 rounded-xl"
|
||||
<button class="deleteBtn bg-red-600/20 text-red-500 hover:bg-red-600/40 text-xs px-3 py-2 rounded-xl transition"
|
||||
data-id="${c.id}">
|
||||
Delete
|
||||
</button>
|
||||
@@ -346,24 +415,70 @@
|
||||
});
|
||||
|
||||
document.querySelectorAll(".editBtn").forEach(btn => {
|
||||
btn.addEventListener("click", async () => {
|
||||
btn.addEventListener("click", () => {
|
||||
const id = btn.dataset.id;
|
||||
const oldName = btn.dataset.name;
|
||||
const oldNotes = btn.dataset.notes;
|
||||
|
||||
const name = prompt("Edit campaign name:", oldName);
|
||||
if (!name) return;
|
||||
|
||||
const notes = prompt("Edit notes:", oldNotes);
|
||||
|
||||
await updateCampaign(id, name, notes);
|
||||
openEditModal(id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openEditModal(id) {
|
||||
const c = allCampaigns.find(camp => camp.id === id);
|
||||
if (!c) return;
|
||||
|
||||
document.getElementById("editCampaignId").value = c.id;
|
||||
document.getElementById("editCampaignName").value = c.name || "";
|
||||
document.getElementById("editCampaignKeywords").value = Array.isArray(c.keywords) ? c.keywords.join(", ") : "";
|
||||
document.getElementById("editCampaignPrompt").value = c.promt || "";
|
||||
document.getElementById("editCampaignWelcomePhotos").checked = c.send_welcome_photos === true;
|
||||
document.getElementById("editCampaignActive").checked = c.active !== false;
|
||||
|
||||
setMessage(document.getElementById("editModalMsg"), "");
|
||||
show(document.getElementById("editCampaignModal"));
|
||||
}
|
||||
|
||||
function closeEditModal() {
|
||||
hide(document.getElementById("editCampaignModal"));
|
||||
}
|
||||
|
||||
async function saveEditedCampaign() {
|
||||
const id = document.getElementById("editCampaignId").value;
|
||||
const name = document.getElementById("editCampaignName").value.trim();
|
||||
const keywordsRaw = document.getElementById("editCampaignKeywords").value.trim();
|
||||
const promt = document.getElementById("editCampaignPrompt").value.trim();
|
||||
const send_welcome_photos = document.getElementById("editCampaignWelcomePhotos").checked;
|
||||
const active = document.getElementById("editCampaignActive").checked;
|
||||
|
||||
const msg = document.getElementById("editModalMsg");
|
||||
|
||||
if (!name) {
|
||||
setMessage(msg, "Name is required.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert comma-separated string to array
|
||||
const keywords = keywordsRaw ? keywordsRaw.split(",").map(k => k.trim()).filter(k => k.length > 0) : [];
|
||||
|
||||
setMessage(msg, "Saving...");
|
||||
|
||||
const { error } = await supabaseClient
|
||||
.from("campaigns")
|
||||
.update({ name, keywords, promt, send_welcome_photos, active })
|
||||
.eq("id", id);
|
||||
|
||||
if (error) {
|
||||
setMessage(msg, "Error: " + error.message, true);
|
||||
return;
|
||||
}
|
||||
|
||||
setMessage(msg, "Saved!", false);
|
||||
closeEditModal();
|
||||
await loadCampaigns();
|
||||
}
|
||||
|
||||
async function createCampaign() {
|
||||
const name = document.getElementById("newCampaignName").value.trim();
|
||||
const notes = document.getElementById("newCampaignNotes").value.trim();
|
||||
const promt = document.getElementById("newCampaignNotes").value.trim();
|
||||
const msg = document.getElementById("campaignCreateMsg");
|
||||
|
||||
if (!name) {
|
||||
@@ -373,7 +488,7 @@
|
||||
|
||||
const { error } = await supabaseClient
|
||||
.from("campaigns")
|
||||
.insert([{ name, notes }]);
|
||||
.insert([{ name, promt, active: true }]);
|
||||
|
||||
if (error) {
|
||||
setMessage(msg, "Error: " + error.message, true);
|
||||
@@ -401,29 +516,12 @@
|
||||
await loadCampaigns();
|
||||
}
|
||||
|
||||
async function updateCampaign(id, name, notes) {
|
||||
const { error } = await supabaseClient
|
||||
.from("campaigns")
|
||||
.update({ name, notes })
|
||||
.eq("id", id);
|
||||
|
||||
if (error) {
|
||||
alert("Error updating campaign: " + error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
await loadCampaigns();
|
||||
}
|
||||
|
||||
// =============================
|
||||
// SESSIONS VIEWER
|
||||
// sessions table must exist
|
||||
// columns: id, campaign_id, created_at, ip, user_agent, etc
|
||||
// =============================
|
||||
async function loadSessions() {
|
||||
const list = document.getElementById("sessionList");
|
||||
list.innerHTML = "";
|
||||
|
||||
const campaignFilter = document.getElementById("sessionsCampaignFilter").value;
|
||||
|
||||
let query = supabaseClient
|
||||
@@ -437,6 +535,9 @@
|
||||
}
|
||||
|
||||
const { data, error } = await query;
|
||||
|
||||
const list = document.getElementById("sessionList");
|
||||
list.innerHTML = "";
|
||||
|
||||
if (error) {
|
||||
list.innerHTML = `<div class="text-red-400 text-sm">Error loading sessions: ${error.message}</div>`;
|
||||
@@ -452,13 +553,25 @@
|
||||
const card = document.createElement("div");
|
||||
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 statusText = s.finished ? "Finished" : "Active";
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<div class="text-sm font-semibold">Session ID: ${s.id}</div>
|
||||
<div class="text-xs text-zinc-400 mt-1">Campaign: ${s.campaign_id || "N/A"}</div>
|
||||
<div class="text-xs text-zinc-500 mt-2">Created: ${formatDate(s.created_at)}</div>
|
||||
<div class="text-xs text-zinc-500 mt-1">IP: ${s.ip || "unknown"}</div>
|
||||
<div class="w-full">
|
||||
<div class="flex justify-between items-center w-full">
|
||||
<div class="text-sm font-semibold text-zinc-200">Phone: ${s.phone || "unknown"}</div>
|
||||
<div class="text-[10px] px-2 py-0.5 rounded-full font-bold uppercase ${statusClass}">
|
||||
${statusText}
|
||||
</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-500 mt-3">Campaign: ${s.campaign_id?.split('-')[0] || "N/A"}...</div>
|
||||
<div class="flex gap-4 mt-1">
|
||||
<div class="text-xs text-zinc-500">Started: ${formatDate(s.created_at)}</div>
|
||||
<div class="text-xs text-zinc-500">Last Interaction: ${s.last_interaction ? formatDate(s.last_interaction) : "None"}</div>
|
||||
</div>
|
||||
<div class="text-xs text-zinc-600 mt-2">Session ID: ${s.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -585,7 +698,16 @@
|
||||
document.getElementById("logoutBtn").addEventListener("click", logout);
|
||||
|
||||
document.getElementById("createCampaignBtn").addEventListener("click", createCampaign);
|
||||
|
||||
document.getElementById("toggleInactiveBtn").addEventListener("click", (e) => {
|
||||
showInactive = !showInactive;
|
||||
e.target.innerText = showInactive ? "Hide Inactive" : "Show Inactive";
|
||||
renderCampaigns();
|
||||
});
|
||||
|
||||
document.getElementById("closeModalBtn").addEventListener("click", closeEditModal);
|
||||
document.getElementById("cancelModalBtn").addEventListener("click", closeEditModal);
|
||||
document.getElementById("saveCampaignBtn").addEventListener("click", saveEditedCampaign);
|
||||
document.getElementById("refreshSessionsBtn").addEventListener("click", loadSessions);
|
||||
document.getElementById("sessionsCampaignFilter").addEventListener("change", loadSessions);
|
||||
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@
|
||||
|
||||
<script>
|
||||
// === CONFIG: fill these in ===
|
||||
const SUPABASE_URL = "http://144.217.160.51:8000";
|
||||
const SUPABASE_URL = "https://rehavit.beroth.moe/supabase";
|
||||
const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzc2NTU4OTc2LCJleHAiOjE5MzQyMzg5NzZ9.eFvvIQUstht8TxNffqAcqfmgS7W2JMZsDxkV41XBPOA";
|
||||
|
||||
const supabaseClient = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
||||
|
||||
Reference in New Issue
Block a user