618 lines
40 KiB
HTML
618 lines
40 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>SMS Campaign Manager</title>
|
||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
|
||
<link rel="stylesheet" href="/static/css/dashboard.css">
|
||
</head>
|
||
<body class="bg-gray-50">
|
||
<div x-data="campaignApp" x-init="init()" x-cloak class="container mx-auto px-4 py-8 max-w-7xl">
|
||
<!-- Header with Connection Status -->
|
||
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
|
||
<div class="flex justify-between items-center">
|
||
<div>
|
||
<h1 class="text-3xl font-bold text-gray-800">📱 SMS Campaign Manager</h1>
|
||
<p class="text-gray-600 mt-1">Homelab Campaign Management Interface</p>
|
||
</div>
|
||
<div class="flex flex-col space-y-2">
|
||
<!-- Termux API Status -->
|
||
<div class="flex items-center">
|
||
<span class="text-sm font-medium text-gray-600 mr-2 w-20">Termux:</span>
|
||
<span x-show="phoneStatus.termux_connected" class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||
🟢 Online
|
||
</span>
|
||
<span x-show="!phoneStatus.termux_connected" class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
||
🔴 Offline
|
||
</span>
|
||
</div>
|
||
|
||
<!-- ADB Status -->
|
||
<div class="flex items-center">
|
||
<span class="text-sm font-medium text-gray-600 mr-2 w-20">ADB:</span>
|
||
<span x-show="phoneStatus.adb_connected" class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||
🟢 Online
|
||
</span>
|
||
<span x-show="!phoneStatus.adb_connected" class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
||
🔴 Offline
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab Navigation -->
|
||
<div class="bg-white rounded-t-lg shadow-sm border-b">
|
||
<nav class="flex space-x-1 p-1">
|
||
<button @click="activeTab = 'campaigns'"
|
||
:class="activeTab === 'campaigns' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
|
||
class="px-4 py-2 rounded-lg font-medium transition-colors">
|
||
📋 Campaigns
|
||
</button>
|
||
<button @click="activeTab = 'conversations'; loadConversations()"
|
||
:class="activeTab === 'conversations' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
|
||
class="px-4 py-2 rounded-lg font-medium transition-colors">
|
||
💬 Conversations
|
||
</button>
|
||
<button @click="activeTab = 'templates'; loadSavedTemplates()"
|
||
:class="activeTab === 'templates' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
|
||
class="px-4 py-2 rounded-lg font-medium transition-colors">
|
||
📝 Templates
|
||
</button>
|
||
<button @click="activeTab = 'lists'; loadSavedLists()"
|
||
:class="activeTab === 'lists' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
|
||
class="px-4 py-2 rounded-lg font-medium transition-colors">
|
||
📋 Contact Lists
|
||
</button>
|
||
<button @click="activeTab = 'testing'"
|
||
:class="activeTab === 'testing' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
|
||
class="px-4 py-2 rounded-lg font-medium transition-colors">
|
||
🧪 System Testing
|
||
</button>
|
||
</nav>
|
||
</div>
|
||
|
||
<!-- Tab Content -->
|
||
<div class="bg-white rounded-b-lg shadow-sm min-h-[600px]">
|
||
<!-- Campaigns Tab (Preserving existing functionality) -->
|
||
<div x-show="activeTab === 'campaigns'" class="p-6">
|
||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<!-- Create Campaign Section -->
|
||
<div class="border rounded-lg p-4">
|
||
<h2 class="text-xl font-semibold mb-4">Create Campaign</h2>
|
||
|
||
<div class="space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">Campaign Name</label>
|
||
<input type="text" x-model="campaignName"
|
||
placeholder="e.g., Weekend Volunteer Outreach"
|
||
class="w-full px-3 py-2 border rounded-lg">
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1 flex items-center gap-2">
|
||
Use Saved Template
|
||
<button @click="console.log('Available templates:', savedTemplates); console.log('Selected template ID:', selectedTemplate); console.log('Current message:', messageTemplate)"
|
||
class="px-2 py-1 text-xs bg-gray-200 text-gray-700 rounded hover:bg-gray-300">Debug</button>
|
||
</label>
|
||
<select x-model="selectedTemplate" @change="loadTemplate($event.target.value)" class="w-full px-3 py-2 border rounded-lg mb-2">
|
||
<option value="">-- Select a saved template --</option>
|
||
<template x-for="template in savedTemplates" :key="template.id">
|
||
<option :value="template.id" x-text="`${template.name} (${template.category})`"></option>
|
||
</template>
|
||
</select>
|
||
<div x-show="selectedTemplate && messageTemplate" class="text-sm text-green-600 mb-2 flex justify-between items-center">
|
||
<span>✓ Template loaded successfully</span>
|
||
<button @click="clearTemplate()" class="text-xs text-gray-500 hover:text-gray-700 underline">
|
||
Clear Template
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||
Message Template <span class="text-gray-500">(Use {name}, {phone}, {date}, {time} for variables)</span>
|
||
</label>
|
||
<textarea x-model="messageTemplate"
|
||
@input="onMessageTemplateChange()"
|
||
placeholder="Hi {name}! Hope all is well. I am wondering if you got my last email..."
|
||
class="w-full px-3 py-2 border rounded-lg h-24"></textarea>
|
||
<div x-show="messageTemplate && messageTemplate.includes('{name}')" class="mt-2 p-2 bg-gray-100 rounded text-sm">
|
||
<span class="font-medium text-gray-600">Preview:</span>
|
||
<span x-text="messageTemplate.replace('{name}', 'John')"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">Recipients CSV</label>
|
||
<input type="file" @change="handleFileUpload($event)"
|
||
accept=".csv"
|
||
class="w-full px-3 py-2 border rounded-lg">
|
||
<div x-show="uploadedFile" class="mt-2 text-sm text-green-600">
|
||
✓ <span x-text="uploadedFile"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Contact Preview Section -->
|
||
<div x-show="contactsPreview.length > 0" class="border rounded-lg p-4 bg-blue-50">
|
||
<h3 class="font-semibold text-blue-800 mb-2">📋 Contacts Preview</h3>
|
||
<div class="text-sm text-blue-600 mb-2">
|
||
Total: <span x-text="totalContacts"></span> contacts loaded
|
||
</div>
|
||
<div class="max-h-40 overflow-y-auto space-y-1">
|
||
<template x-for="(contact, index) in contactsPreview" :key="`preview-${index}`">
|
||
<div class="text-sm bg-white p-2 rounded border">
|
||
<span class="font-medium" x-text="contact.name || 'No Name'"></span> -
|
||
<span class="text-gray-600" x-text="contact.phone"></span>
|
||
<div x-show="contact.preview_message" class="text-xs text-gray-500 mt-1">
|
||
Preview: <span x-text="(contact.preview_message || '').substring(0, 50) + '...'"></span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<div x-show="totalContacts > contactsPreview.length" class="text-xs text-blue-500 mt-2">
|
||
... and <span x-text="totalContacts - contactsPreview.length"></span> more contacts
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">Use Saved List</label>
|
||
<select x-model="selectedList" @change="loadSavedList($event.target.value)" class="w-full px-3 py-2 border rounded-lg">
|
||
<option value="">-- Select a saved list --</option>
|
||
<template x-for="(list, index) in savedLists" :key="`list-${index}-${list.id || ''}`">
|
||
<option :value="list.id" x-text="`${list.name} (${list.total_contacts || 0} contacts)`"></option>
|
||
</template>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="flex gap-2">
|
||
<button @click="saveTemplate()"
|
||
class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600">
|
||
Save Template
|
||
</button>
|
||
<button @click="testSMS()"
|
||
class="bg-yellow-500 text-white px-4 py-2 rounded hover:bg-yellow-600">
|
||
Test SMS
|
||
</button>
|
||
<button @click="startCampaign()"
|
||
:disabled="!campaignReady"
|
||
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 disabled:opacity-50">
|
||
Start Campaign
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Campaign Status Section -->
|
||
<div class="space-y-4">
|
||
<!-- Analytics -->
|
||
<div class="border rounded-lg p-4">
|
||
<h3 class="font-semibold mb-3">Campaign Analytics</h3>
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div class="text-center">
|
||
<div class="text-2xl font-bold text-blue-600" x-text="analytics.total_sent || 0"></div>
|
||
<div class="text-sm text-gray-600">Total Sent</div>
|
||
</div>
|
||
<div class="text-center">
|
||
<div class="text-2xl font-bold text-green-600" x-text="analytics.responses || 0"></div>
|
||
<div class="text-sm text-gray-600">Responses</div>
|
||
</div>
|
||
<div class="text-center">
|
||
<div class="text-2xl font-bold text-yellow-600" x-text="analytics.follow_ups || 0"></div>
|
||
<div class="text-sm text-gray-600">Follow-ups Needed</div>
|
||
</div>
|
||
<div class="text-center">
|
||
<div class="text-2xl font-bold text-purple-600" x-text="analytics.opt_outs || 0"></div>
|
||
<div class="text-sm text-gray-600">Opt-outs</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Response Types -->
|
||
<div class="border rounded-lg p-4">
|
||
<h3 class="font-semibold mb-3">Response Types</h3>
|
||
<div x-show="!responseTypes.length" class="text-gray-500 text-sm">
|
||
No responses yet
|
||
</div>
|
||
<div class="space-y-2">
|
||
<template x-for="type in responseTypes" :key="type.type">
|
||
<div class="flex justify-between items-center">
|
||
<span x-text="type.type" class="text-sm"></span>
|
||
<span x-text="type.count" class="font-medium"></span>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Recent Campaigns -->
|
||
<div class="border rounded-lg p-4">
|
||
<h3 class="font-semibold mb-3">Recent Campaigns</h3>
|
||
<div x-show="!recentCampaigns.length" class="text-gray-500 text-sm">
|
||
No recent campaigns
|
||
</div>
|
||
<div class="space-y-2">
|
||
<template x-for="(campaign, index) in recentCampaigns" :key="`campaign-${index}-${campaign.id || ''}`">
|
||
<div class="border-b pb-2">
|
||
<div class="font-medium" x-text="campaign.name"></div>
|
||
<div class="text-sm text-gray-600">
|
||
<span x-text="campaign.sent_count || 0"></span> sent •
|
||
<span x-text="formatDate(campaign.created_at)"></span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Conversations Tab -->
|
||
<div x-show="activeTab === 'conversations'" class="p-6">
|
||
<div id="conversations-container">
|
||
<!-- Enhanced conversations component will be loaded here -->
|
||
<include src="conversations_enhanced_component.html"></include>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- System Testing Tab -->
|
||
<div x-show="activeTab === 'testing'" class="p-6">
|
||
<h2 class="text-xl font-semibold mb-4">🧪 System Testing & Diagnostics</h2>
|
||
|
||
<!-- Connection Tests -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||
<!-- Termux API Test -->
|
||
<div class="border rounded-lg p-4">
|
||
<h3 class="font-medium mb-3">📡 Termux API Test</h3>
|
||
<div class="text-sm text-gray-600 mb-3">
|
||
Endpoint: <code class="bg-gray-100 px-1">http://{{ phone_ip }}:5001</code>
|
||
</div>
|
||
<button @click="testTermuxConnection()"
|
||
:disabled="testingTermux"
|
||
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 disabled:opacity-50 transition-colors">
|
||
<span x-show="!testingTermux">Test Termux API</span>
|
||
<span x-show="testingTermux">Testing...</span>
|
||
</button>
|
||
<div x-show="termuxTestResult" class="mt-3 p-3 rounded text-sm"
|
||
:class="termuxTestResult?.success ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'">
|
||
<pre x-text="JSON.stringify(termuxTestResult, null, 2)"></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ADB Connection Test -->
|
||
<div class="border rounded-lg p-4">
|
||
<h3 class="font-medium mb-3">🔌 ADB Connection Test</h3>
|
||
<div class="text-sm text-gray-600 mb-3">
|
||
Device: <code class="bg-gray-100 px-1">{{ phone_ip }}:5555</code>
|
||
</div>
|
||
<button @click="testAdbConnection()"
|
||
:disabled="testingAdb"
|
||
class="bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600 disabled:opacity-50 transition-colors">
|
||
<span x-show="!testingAdb">Test ADB</span>
|
||
<span x-show="testingAdb">Testing...</span>
|
||
</button>
|
||
<div x-show="adbTestResult" class="mt-3 p-3 rounded text-sm"
|
||
:class="adbTestResult?.success ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'">
|
||
<pre x-text="JSON.stringify(adbTestResult, null, 2)"></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Test SMS Send -->
|
||
<div class="border rounded-lg p-4 mb-6">
|
||
<h3 class="font-medium mb-3">📨 Test SMS Send</h3>
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<input type="tel" x-model="testPhone"
|
||
placeholder="Phone number (e.g., 7801234567)"
|
||
class="border rounded px-3 py-2">
|
||
<input type="text" x-model="testMessage"
|
||
placeholder="Test message"
|
||
class="border rounded px-3 py-2">
|
||
</div>
|
||
<div class="mt-4 flex gap-2">
|
||
<button @click="sendTestSms('termux')"
|
||
:disabled="!testPhone || sendingTest"
|
||
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600 disabled:opacity-50 transition-colors">
|
||
Send via Termux
|
||
</button>
|
||
<button @click="sendTestSms('adb')"
|
||
:disabled="!testPhone || sendingTest"
|
||
class="bg-purple-500 text-white px-4 py-2 rounded hover:bg-purple-600 disabled:opacity-50 transition-colors">
|
||
Send via ADB
|
||
</button>
|
||
<button @click="sendTestSms('auto')"
|
||
:disabled="!testPhone || sendingTest"
|
||
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 disabled:opacity-50 transition-colors">
|
||
Auto (Best Available)
|
||
</button>
|
||
</div>
|
||
<div x-show="testSmsResult" class="mt-3 p-3 rounded text-sm"
|
||
:class="testSmsResult?.success ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'">
|
||
<pre x-text="JSON.stringify(testSmsResult, null, 2)"></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- System Information -->
|
||
<div class="border rounded-lg p-4">
|
||
<h3 class="font-medium mb-3">ℹ️ System Information</h3>
|
||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||
<div>
|
||
<span class="font-medium">Phone IP:</span>
|
||
<span x-text="phoneIP"></span>
|
||
</div>
|
||
<div>
|
||
<span class="font-medium">Preferred Method:</span>
|
||
<span x-text="phoneStatus.prefer_termux ? 'Termux API' : 'ADB'"></span>
|
||
</div>
|
||
<div>
|
||
<span class="font-medium">Last Check:</span>
|
||
<span x-text="formatTime(phoneStatus.last_check)"></span>
|
||
</div>
|
||
<div>
|
||
<span class="font-medium">Active Connection:</span>
|
||
<span x-text="phoneStatus.termux_connected ? 'Termux' : (phoneStatus.adb_connected ? 'ADB' : 'None')"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Templates Tab -->
|
||
<div x-show="activeTab === 'templates'" class="p-6">
|
||
<div class="max-w-6xl mx-auto">
|
||
<h2 class="text-2xl font-bold text-gray-800 mb-6">📝 Message Templates</h2>
|
||
|
||
<!-- Create/Edit Template Form -->
|
||
<div class="mb-8 p-6 border rounded-lg bg-blue-50">
|
||
<h3 class="font-semibold text-gray-700 mb-4">
|
||
<span x-show="!editingTemplate">Create New Template</span>
|
||
<span x-show="editingTemplate">Edit Template</span>
|
||
</h3>
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">Template Name</label>
|
||
<input type="text" x-model="templateForm.name"
|
||
placeholder="e.g., Volunteer Check-In"
|
||
class="w-full px-3 py-2 border rounded-lg">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">Category</label>
|
||
<select x-model="templateForm.category" class="w-full px-3 py-2 border rounded-lg">
|
||
<option value="general">General</option>
|
||
<option value="volunteer">Volunteer</option>
|
||
<option value="reminder">Reminder</option>
|
||
<option value="gratitude">Gratitude</option>
|
||
<option value="followup">Follow-up</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
|
||
<input type="text" x-model="templateForm.description"
|
||
placeholder="Brief description of when to use this template"
|
||
class="w-full px-3 py-2 border rounded-lg">
|
||
</div>
|
||
<div class="mb-4">
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||
Message Template <span class="text-gray-500">(Use {name} for personalization)</span>
|
||
</label>
|
||
<textarea x-model="templateForm.content"
|
||
placeholder="Hi {name}! Your message here..."
|
||
class="w-full px-3 py-2 border rounded-lg h-24"></textarea>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button @click="saveNewTemplate()"
|
||
:disabled="!templateForm.name || !templateForm.content"
|
||
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 disabled:opacity-50">
|
||
<span x-show="!editingTemplate">Save Template</span>
|
||
<span x-show="editingTemplate">Update Template</span>
|
||
</button>
|
||
<button x-show="editingTemplate" @click="cancelEditTemplate()"
|
||
class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600">
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Templates List -->
|
||
<div>
|
||
<h3 class="font-semibold text-gray-700 mb-4">Saved Templates</h3>
|
||
<div x-show="savedTemplates.length === 0" class="text-gray-500 text-center py-8 border rounded-lg">
|
||
No templates saved yet. Create one above to get started!
|
||
</div>
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<template x-for="template in savedTemplates" :key="template.id">
|
||
<div class="border rounded-lg p-4 hover:bg-gray-50 transition-colors">
|
||
<div class="flex justify-between items-start mb-3">
|
||
<div class="flex-1">
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<h4 class="font-medium text-gray-800" x-text="template.name"></h4>
|
||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-gray-100 text-gray-800"
|
||
x-text="template.category"></span>
|
||
<span x-show="template.is_favorite" class="text-yellow-500">⭐</span>
|
||
</div>
|
||
<p x-show="template.description" class="text-sm text-gray-600 mb-2" x-text="template.description"></p>
|
||
</div>
|
||
</div>
|
||
<div class="text-sm text-gray-700 bg-white p-3 rounded border mb-3 italic">
|
||
"<span x-text="template.template || template.content"></span>"
|
||
</div>
|
||
<div class="flex justify-between items-center text-xs text-gray-500 mb-3">
|
||
<span>Used <span x-text="template.usage_count || 0"></span> times</span>
|
||
<span>Created <span x-text="formatDate(template.created_at)"></span></span>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<button @click="loadTemplateForEditing(template)"
|
||
class="text-blue-600 hover:text-blue-800 text-sm px-2 py-1 border border-blue-200 rounded hover:bg-blue-50">
|
||
Edit
|
||
</button>
|
||
<button @click="toggleTemplateFavorite(template)"
|
||
class="text-yellow-600 hover:text-yellow-800 text-sm px-2 py-1 border border-yellow-200 rounded hover:bg-yellow-50">
|
||
<span x-show="template.is_favorite">Unfav</span>
|
||
<span x-show="!template.is_favorite">Fav</span>
|
||
</button>
|
||
<button @click="deleteTemplate(template.id, template.name)"
|
||
class="text-red-600 hover:text-red-800 text-sm px-2 py-1 border border-red-200 rounded hover:bg-red-50">
|
||
Delete
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Contact Lists Tab -->
|
||
<div x-show="activeTab === 'lists'" class="p-6">
|
||
<div class="max-w-6xl mx-auto">
|
||
<h2 class="text-2xl font-bold text-gray-800 mb-6">📋 Contact Lists</h2>
|
||
|
||
<!-- Upload CSV Section -->
|
||
<div class="mb-8 p-6 border rounded-lg bg-green-50">
|
||
<h3 class="font-semibold text-gray-700 mb-4">Upload New Contact List</h3>
|
||
<div class="flex flex-col md:flex-row gap-4">
|
||
<div class="flex-1">
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">CSV File</label>
|
||
<input type="file" @change="handleListUpload($event)"
|
||
accept=".csv"
|
||
class="w-full px-3 py-2 border rounded-lg">
|
||
</div>
|
||
<div class="flex-1">
|
||
<label class="block text-sm font-medium text-gray-700 mb-1">List Name (optional)</label>
|
||
<input type="text" x-model="listUploadName"
|
||
placeholder="Leave blank for auto-generated name"
|
||
class="w-full px-3 py-2 border rounded-lg">
|
||
</div>
|
||
</div>
|
||
<div x-show="listUploadPreview.length > 0" class="mt-4 p-4 bg-white rounded border">
|
||
<h4 class="font-medium mb-2">Preview (<span x-text="listUploadPreview.length"></span> contacts)</h4>
|
||
<div class="max-h-40 overflow-y-auto space-y-2">
|
||
<template x-for="(contact, index) in listUploadPreview" :key="`preview-${index}`">
|
||
<div class="text-sm bg-gray-50 p-2 rounded border flex justify-between">
|
||
<div>
|
||
<span class="font-medium" x-text="contact.name || 'No Name'"></span>
|
||
<span class="text-gray-600 ml-2" x-text="contact.phone"></span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<button @click="saveListFromPreview()"
|
||
class="mt-3 bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
|
||
Save List
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Lists Display -->
|
||
<div>
|
||
<div class="flex justify-between items-center mb-4">
|
||
<h3 class="font-semibold text-gray-700">Saved Lists</h3>
|
||
<button @click="loadSavedLists()" class="text-blue-600 hover:text-blue-800 text-sm">
|
||
🔄 Refresh
|
||
</button>
|
||
</div>
|
||
|
||
<div x-show="!savedLists || savedLists.length === 0" class="text-gray-500 text-center py-8 border rounded-lg">
|
||
No contact lists saved yet. Upload a CSV file above to get started!
|
||
</div>
|
||
|
||
<div x-show="savedLists && savedLists.length > 0" class="grid grid-cols-1 gap-4">
|
||
<template x-for="list in savedLists" :key="list.id">
|
||
<div class="border rounded-lg p-4 hover:bg-gray-50 transition-colors">
|
||
<div class="flex justify-between items-start mb-3">
|
||
<div class="flex-1">
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<h4 class="font-medium text-gray-800" x-text="list.name"></h4>
|
||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
|
||
x-text="`${list.total_contacts} contacts`"></span>
|
||
</div>
|
||
<p class="text-sm text-gray-600" x-text="`From: ${list.original_filename}`"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex justify-between items-center text-xs text-gray-500 mb-3">
|
||
<span>Used <span x-text="list.usage_count || 0"></span> times</span>
|
||
<span>Created <span x-text="formatDate(list.created_at)"></span></span>
|
||
<span x-show="list.last_used_at">Last used: <span x-text="formatDate(list.last_used_at)"></span></span>
|
||
</div>
|
||
|
||
<div class="flex gap-2">
|
||
<button @click="viewListContacts(list)"
|
||
class="text-blue-600 hover:text-blue-800 text-sm px-3 py-1 border border-blue-200 rounded hover:bg-blue-50">
|
||
👁 View
|
||
</button>
|
||
<button @click="useListForCampaign(list)"
|
||
class="text-green-600 hover:text-green-800 text-sm px-3 py-1 border border-green-200 rounded hover:bg-green-50">
|
||
📤 Use for Campaign
|
||
</button>
|
||
<button @click="downloadList(list)"
|
||
class="text-purple-600 hover:text-purple-800 text-sm px-3 py-1 border border-purple-200 rounded hover:bg-purple-50">
|
||
💾 Download
|
||
</button>
|
||
<button @click="deleteContactList(list.id, list.name)"
|
||
class="text-red-600 hover:text-red-800 text-sm px-3 py-1 border border-red-200 rounded hover:bg-red-50">
|
||
🗑 Delete
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
|
||
<!-- List Detail Modal -->
|
||
<div x-show="viewingList" x-cloak class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div class="bg-white rounded-lg p-6 max-w-4xl max-h-[80vh] w-full mx-4 overflow-hidden flex flex-col">
|
||
<div class="flex justify-between items-center mb-4">
|
||
<h3 class="text-lg font-semibold">
|
||
<span x-text="viewingList ? viewingList.name : ''"></span>
|
||
<span class="text-sm font-normal text-gray-600">
|
||
(<span x-text="viewingList ? viewingList.total_contacts : 0"></span> contacts)
|
||
</span>
|
||
</h3>
|
||
<button @click="viewingList = null" class="text-gray-500 hover:text-gray-700">✕</button>
|
||
</div>
|
||
|
||
<div class="flex-1 overflow-y-auto">
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||
<template x-for="(contact, index) in viewingListContacts" :key="`contact-${index}`">
|
||
<div class="border rounded p-3 bg-gray-50">
|
||
<div class="font-medium" x-text="contact.name || 'No Name'"></div>
|
||
<div class="text-gray-600" x-text="contact.phone"></div>
|
||
<div x-show="contact.email" class="text-gray-500 text-sm" x-text="contact.email"></div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-4 pt-4 border-t flex justify-end gap-2">
|
||
<button @click="viewingList = null"
|
||
class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600">
|
||
Close
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Load JavaScript files -->
|
||
<script src="/static/js/dashboard.js?v=2025082505"></script>
|
||
<script src="/static/js/lists.js?v=2025082505"></script>
|
||
<script src="/static/js/conversations_enhanced.js?v=2025082505"></script>
|
||
<script>
|
||
// Initialize phone IP from template
|
||
document.addEventListener('alpine:init', () => {
|
||
Alpine.data('campaignApp', () => {
|
||
const app = campaignApp();
|
||
app.phoneIP = '{{ phone_ip }}';
|
||
return app;
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|