mirror of
https://git.datalinker.icu/deepseek-ai/DeepSeek-V3.git
synced 2026-01-23 14:04:26 +08:00
3572 lines
123 KiB
HTML
3572 lines
123 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<!-- Head section remains the same -->
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
<title>Message Tracking System</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
|
<style>
|
|
/* All existing CSS remains the same */
|
|
:root {
|
|
--primary: #2563eb;
|
|
--primary-dark: #1d4ed8;
|
|
--primary-light: #dbeafe;
|
|
--success: #10b981;
|
|
--warning: #f59e0b;
|
|
--danger: #ef4444;
|
|
--info: #06b6d4;
|
|
--bg: #f8fafc;
|
|
--surface: #ffffff;
|
|
--text-primary: #1e293b;
|
|
--text-secondary: #64748b;
|
|
--border: #e2e8f0;
|
|
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
--whatsapp: #25D366;
|
|
--whatsapp-dark: #128C7E;
|
|
--crispy-red: #ff6b6b;
|
|
--crispy-orange: #feca57;
|
|
}
|
|
/* All existing CSS rules remain the same */
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
background-color: var(--bg);
|
|
color: var(--text-primary);
|
|
line-height: 1.6;
|
|
}
|
|
/* All existing CSS rules remain the same */
|
|
|
|
/* New CSS for improvements */
|
|
.quick-stats {
|
|
display: flex;
|
|
gap: 1rem;
|
|
margin-bottom: 1.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.quick-stat-card {
|
|
background: var(--surface);
|
|
border-radius: 12px;
|
|
padding: 1rem;
|
|
border: 1px solid var(--border);
|
|
box-shadow: var(--shadow);
|
|
flex: 1;
|
|
min-width: 150px;
|
|
text-align: center;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.quick-stat-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
.quick-stat-value {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.quick-stat-label {
|
|
font-size: 0.75rem;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.recent-activity {
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.activity-item {
|
|
padding: 0.75rem;
|
|
border-bottom: 1px solid var(--border);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.activity-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.activity-icon {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.activity-icon.sent {
|
|
background: rgba(16, 185, 129, 0.1);
|
|
color: var(--success);
|
|
}
|
|
|
|
.activity-icon.delivered {
|
|
background: rgba(6, 182, 212, 0.1);
|
|
color: var(--info);
|
|
}
|
|
|
|
.activity-icon.failed {
|
|
background: rgba(239, 68, 68, 0.1);
|
|
color: var(--danger);
|
|
}
|
|
|
|
.activity-icon.crispy {
|
|
background: rgba(255, 107, 107, 0.1);
|
|
color: var(--crispy-red);
|
|
}
|
|
|
|
.activity-content {
|
|
flex: 1;
|
|
}
|
|
|
|
.activity-title {
|
|
font-weight: 500;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.activity-time {
|
|
font-size: 0.75rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.bulk-actions {
|
|
margin-bottom: 1rem;
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.dark-mode {
|
|
--bg: #1e293b;
|
|
--surface: #334155;
|
|
--text-primary: #f1f5f9;
|
|
--text-secondary: #94a3b8;
|
|
--border: #475569;
|
|
}
|
|
|
|
.dark-mode .card {
|
|
background: var(--surface);
|
|
border-color: var(--border);
|
|
}
|
|
|
|
.dark-mode .form-control,
|
|
.dark-mode .form-select {
|
|
background: var(--bg);
|
|
border-color: var(--border);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.dark-mode .table {
|
|
background: var(--surface);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.dark-mode .table thead {
|
|
background: rgba(37, 99, 235, 0.2);
|
|
}
|
|
|
|
.dark-mode .table tbody tr:hover {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.notification-badge {
|
|
position: absolute;
|
|
top: -5px;
|
|
right: -5px;
|
|
background: var(--danger);
|
|
color: white;
|
|
border-radius: 50%;
|
|
width: 18px;
|
|
height: 18px;
|
|
font-size: 0.7rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.message-simulation {
|
|
background: var(--bg);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.simulation-controls {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.simulation-log {
|
|
background: var(--surface);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
font-family: monospace;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.log-entry {
|
|
margin-bottom: 0.5rem;
|
|
padding-bottom: 0.5rem;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.log-entry:last-child {
|
|
border-bottom: none;
|
|
margin-bottom: 0;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.log-time {
|
|
color: var(--text-secondary);
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
.log-status {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.log-status.success {
|
|
color: var(--success);
|
|
}
|
|
|
|
.log-status.error {
|
|
color: var(--danger);
|
|
}
|
|
|
|
.log-status.info {
|
|
color: var(--info);
|
|
}
|
|
|
|
.log-status.warning {
|
|
color: var(--warning);
|
|
}
|
|
|
|
.customer-search {
|
|
position: relative;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.customer-search input {
|
|
padding-left: 2.5rem;
|
|
}
|
|
|
|
.customer-search i {
|
|
position: absolute;
|
|
left: 0.75rem;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.customer-filters {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.customer-filter-btn {
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 20px;
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
color: var(--text-primary);
|
|
font-size: 0.875rem;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.customer-filter-btn:hover {
|
|
background: var(--primary-light);
|
|
}
|
|
|
|
.customer-filter-btn.active {
|
|
background: var(--primary);
|
|
color: white;
|
|
}
|
|
|
|
.customer-item.selected {
|
|
background: var(--primary-light);
|
|
}
|
|
|
|
.bulk-customer-actions {
|
|
margin-top: 1rem;
|
|
padding-top: 1rem;
|
|
border-top: 1px solid var(--border);
|
|
display: none;
|
|
}
|
|
|
|
.bulk-customer-actions.active {
|
|
display: block;
|
|
}
|
|
|
|
.offer-scheduler {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.scheduled-offers {
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.scheduled-offer-item {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
margin-bottom: 0.75rem;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.scheduled-offer-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.scheduled-offer-time {
|
|
font-size: 0.875rem;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.scheduled-offer-message {
|
|
margin-top: 0.5rem;
|
|
font-size: 0.875rem;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.scheduled-offer-actions {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Page Header -->
|
|
<div class="page-header">
|
|
<div class="main-container">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h1 class="page-title">Message Tracking System</h1>
|
|
<p class="page-subtitle">Monitor and track all message delivery status with real-time analytics</p>
|
|
</div>
|
|
<div class="d-flex gap-3">
|
|
<button class="btn btn-light" onclick="exportReport()">
|
|
<i class="bi bi-download"></i> Export Report
|
|
</button>
|
|
<button class="btn btn-light position-relative" onclick="showNotifications()">
|
|
<i class="bi bi-bell"></i> Notifications
|
|
<span class="notification-badge" id="notificationBadge">3</span>
|
|
</button>
|
|
<button class="btn btn-light" onclick="toggleDarkMode()">
|
|
<i class="bi bi-moon-stars" id="darkModeIcon"></i>
|
|
</button>
|
|
<button class="btn btn-light" onclick="showSettings()">
|
|
<i class="bi bi-gear"></i> Settings
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<div class="main-container">
|
|
<!-- Quick Stats Section -->
|
|
<div class="quick-stats">
|
|
<div class="quick-stat-card">
|
|
<div class="quick-stat-value" id="todaySent">0</div>
|
|
<div class="quick-stat-label">Today's Sent</div>
|
|
</div>
|
|
<div class="quick-stat-card">
|
|
<div class="quick-stat-value" id="todayDelivered">0</div>
|
|
<div class="quick-stat-label">Today's Delivered</div>
|
|
</div>
|
|
<div class="quick-stat-card">
|
|
<div class="quick-stat-value" id="todayFailed">0</div>
|
|
<div class="quick-stat-label">Today's Failed</div>
|
|
</div>
|
|
<div class="quick-stat-card">
|
|
<div class="quick-stat-value" id="todayOffers">0</div>
|
|
<div class="quick-stat-label">Today's Offers</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Customer Management Section -->
|
|
<div class="customer-section">
|
|
<div class="customer-header">
|
|
<h2 class="customer-title">
|
|
<i class="bi bi-people"></i> Customer Management
|
|
</h2>
|
|
<div>
|
|
<button class="btn btn-sm btn-primary" onclick="toggleCustomerForm()">
|
|
<i class="bi bi-plus"></i> Add Customer
|
|
</button>
|
|
<button class="btn btn-sm btn-success" onclick="showUploadModal()">
|
|
<i class="bi bi-upload"></i> Upload Data
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="customer-body">
|
|
<!-- Add Customer Form -->
|
|
<div class="customer-form" id="customerForm" style="display: none;">
|
|
<h5 class="mb-3">Add New Customer</h5>
|
|
<form id="addCustomerForm">
|
|
<div class="row">
|
|
<div class="col-md-4 mb-3">
|
|
<label for="customerName" class="form-label">Full Name</label>
|
|
<input type="text" class="form-control" id="customerName" required>
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label for="customerPhone" class="form-label">Phone Number</label>
|
|
<input type="tel" class="form-control" id="customerPhone" required>
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label for="customerEmail" class="form-label">Email</label>
|
|
<input type="email" class="form-control" id="customerEmail">
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<label for="customerCity" class="form-label">City</label>
|
|
<input type="text" class="form-control" id="customerCity">
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label for="customerStatus" class="form-label">Status</label>
|
|
<select class="form-select" id="customerStatus">
|
|
<option value="active">Active</option>
|
|
<option value="inactive">Inactive</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-save"></i> Save Customer
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" onclick="toggleCustomerForm()">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Customer Search and Filters -->
|
|
<div class="customer-search">
|
|
<i class="bi bi-search"></i>
|
|
<input type="text" class="form-control" id="customerSearchInput" placeholder="Search customers...">
|
|
</div>
|
|
|
|
<div class="customer-filters">
|
|
<button class="customer-filter-btn active" data-filter="all">All Customers</button>
|
|
<button class="customer-filter-btn" data-filter="active">Active</button>
|
|
<button class="customer-filter-btn" data-filter="inactive">Inactive</button>
|
|
<button class="customer-filter-btn" data-filter="dubai">Dubai</button>
|
|
<button class="customer-filter-btn" data-filter="abudhabi">Abu Dhabi</button>
|
|
<button class="customer-filter-btn" data-filter="sharjah">Sharjah</button>
|
|
</div>
|
|
|
|
<!-- Bulk Actions -->
|
|
<div class="bulk-customer-actions" id="bulkCustomerActions">
|
|
<h6>Bulk Actions</h6>
|
|
<div class="d-flex gap-2">
|
|
<button class="btn btn-sm btn-success" onclick="bulkChangeStatus('active')">
|
|
<i class="bi bi-check-circle"></i> Set Active
|
|
</button>
|
|
<button class="btn btn-sm btn-warning" onclick="bulkChangeStatus('inactive')">
|
|
<i class="bi bi-x-circle"></i> Set Inactive
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="bulkDeleteCustomers()">
|
|
<i class="bi bi-trash"></i> Delete Selected
|
|
</button>
|
|
<button class="btn btn-sm btn-primary" onclick="bulkSendOffer()">
|
|
<i class="bi bi-send"></i> Send Offer
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Customer List -->
|
|
<div class="customer-list" id="customerList">
|
|
<!-- Customers will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- WhatsApp Offer System -->
|
|
<div class="whatsapp-offer">
|
|
<div class="whatsapp-offer-header">
|
|
<div>
|
|
<h2 class="whatsapp-offer-title">
|
|
<i class="bi bi-whatsapp"></i> WhatsApp Offer System
|
|
</h2>
|
|
<p>Send promotional offers to all customers via WhatsApp Business</p>
|
|
</div>
|
|
<div>
|
|
<img src="https://upload.wikimedia.org/wikipedia/commons/6/6b/WhatsApp.svg" alt="WhatsApp" width="50">
|
|
</div>
|
|
</div>
|
|
<div class="whatsapp-offer-form">
|
|
<form id="whatsappOfferForm">
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label for="whatsappNumber" class="whatsapp-offer-label">WhatsApp Business Number</label>
|
|
<input type="text" class="form-control whatsapp-offer-input" id="whatsappNumber" value="0501882206">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="whatsapp-offer-label">Customers Count</label>
|
|
<input type="text" class="form-control whatsapp-offer-input" id="customerCount" readonly>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="whatsapp-offer-label">Offer Templates</label>
|
|
<div class="template-selector">
|
|
<button type="button" class="template-btn active" onclick="selectTemplate('crispy')">CRISPY CHICKEN Offer</button>
|
|
<button type="button" class="template-btn" onclick="selectTemplate('special')">Special Discount</button>
|
|
<button type="button" class="template-btn" onclick="selectTemplate('new')">New Product</button>
|
|
<button type="button" class="template-btn" onclick="selectTemplate('custom')">Custom Template</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="offerMessage" class="whatsapp-offer-label">Offer Message</label>
|
|
<textarea class="form-control whatsapp-offer-textarea" id="offerMessage" rows="10">🔥 CRISPY CHICKEN - ULTIMATE OFFER 🔥
|
|
🍗 12 Crispy Chicken Pieces + 3 Garlic Sauce
|
|
Only 30 AED
|
|
⏰ Valid: Monday & Tuesday Only
|
|
🎁 Special Bonus!
|
|
Get a FREE drink with every order above 50 AED!
|
|
📞 To Order Directly: 600527488
|
|
Order Now!
|
|
⏳ Limited Time!
|
|
Order within 2 hours only!
|
|
👉 Share with friends & get extra discount!
|
|
#CrispyChicken #SpecialOffer #CrispyChickenDeal</textarea>
|
|
</div>
|
|
|
|
<!-- Offer Scheduler -->
|
|
<div class="offer-scheduler">
|
|
<h5 class="mb-3">Schedule Offer</h5>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label for="scheduleDate" class="whatsapp-offer-label">Date</label>
|
|
<input type="date" class="form-control whatsapp-offer-input" id="scheduleDate">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label for="scheduleTime" class="whatsapp-offer-label">Time</label>
|
|
<input type="time" class="form-control whatsapp-offer-input" id="scheduleTime">
|
|
</div>
|
|
</div>
|
|
<div class="form-check mt-2">
|
|
<input class="form-check-input" type="checkbox" id="scheduleOffer">
|
|
<label class="form-check-label" for="scheduleOffer">
|
|
Schedule this offer for later
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="whatsapp-offer-label">Message Preview</label>
|
|
<div class="offer-preview">
|
|
<div class="offer-preview-title">CRISPY CHICKEN Offer Preview</div>
|
|
<div class="offer-preview-content" id="offerPreview">🔥 CRISPY CHICKEN - ULTIMATE OFFER 🔥
|
|
🍗 12 Crispy Chicken Pieces + 3 Garlic Sauce
|
|
Only 30 AED
|
|
⏰ Valid: Monday & Tuesday Only
|
|
🎁 Special Bonus!
|
|
Get a FREE drink with every order above 50 AED!
|
|
📞 To Order Directly: 600527488
|
|
Order Now!
|
|
⏳ Limited Time!
|
|
Order within 2 hours only!
|
|
👉 Share with friends & get extra discount!
|
|
#CrispyChicken #SpecialOffer #CrispyChickenDeal</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scheduled Offers -->
|
|
<div class="scheduled-offers" id="scheduledOffers" style="display: none;">
|
|
<h5 class="mb-3">Scheduled Offers</h5>
|
|
<div id="scheduledOffersList">
|
|
<!-- Scheduled offers will be populated here -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="whatsapp-stats">
|
|
<div class="whatsapp-stat">
|
|
<div class="whatsapp-stat-value" id="totalSentOffers">0</div>
|
|
<div class="whatsapp-stat-label">Offers Sent</div>
|
|
</div>
|
|
<div class="whatsapp-stat">
|
|
<div class="whatsapp-stat-value" id="totalDeliveredOffers">0</div>
|
|
<div class="whatsapp-stat-label">Delivered</div>
|
|
</div>
|
|
<div class="whatsapp-stat">
|
|
<div class="whatsapp-stat-value" id="totalFailedOffers">0</div>
|
|
<div class="whatsapp-stat-label">Failed</div>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-crispy">
|
|
<i class="bi bi-send"></i> Send Offer to All Customers
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Dashboard -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card customers">
|
|
<div class="stat-value" id="totalCustomers">0</div>
|
|
<div class="stat-label">Total Customers</div>
|
|
<div class="stat-change positive">
|
|
<i class="bi bi-arrow-up"></i> +5% this week
|
|
</div>
|
|
</div>
|
|
<div class="stat-card sent">
|
|
<div class="stat-value" id="totalSent">0</div>
|
|
<div class="stat-label">Messages Sent</div>
|
|
<div class="stat-change positive">
|
|
<i class="bi bi-arrow-up"></i> +15% from yesterday
|
|
</div>
|
|
</div>
|
|
<div class="stat-card pending">
|
|
<div class="stat-value" id="totalPending">0</div>
|
|
<div class="stat-label">Pending</div>
|
|
<div class="stat-change">
|
|
<i class="bi bi-clock"></i> Processing...
|
|
</div>
|
|
</div>
|
|
<div class="stat-card delivered">
|
|
<div class="stat-value" id="totalDelivered">0</div>
|
|
<div class="stat-label">Delivered</div>
|
|
<div class="stat-change positive">
|
|
<i class="bi bi-arrow-up"></i> 92% success rate
|
|
</div>
|
|
</div>
|
|
<div class="stat-card failed">
|
|
<div class="stat-value" id="totalFailed">0</div>
|
|
<div class="stat-label">Failed</div>
|
|
<div class="stat-change negative">
|
|
<i class="bi bi-arrow-down"></i> 8% failure rate
|
|
</div>
|
|
</div>
|
|
<div class="stat-card crispy">
|
|
<div class="stat-value" id="totalCrispy">0</div>
|
|
<div class="stat-label">CRISPY Offers</div>
|
|
<div class="stat-change positive">
|
|
<i class="bi bi-arrow-up"></i> Hot deals!
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts and Recent Activity -->
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">
|
|
<i class="bi bi-graph-up"></i> Delivery Analytics
|
|
</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="chart-container">
|
|
<canvas id="deliveryChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">
|
|
<i class="bi bi-activity"></i> Recent Activity
|
|
</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="recent-activity" id="recentActivity">
|
|
<!-- Recent activity will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Message Simulation Section -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2 class="card-title">
|
|
<i class="bi bi-cpu"></i> Message Delivery Simulation
|
|
</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="simulation-controls">
|
|
<button class="btn btn-primary" onclick="startSimulation()">
|
|
<i class="bi bi-play"></i> Start Simulation
|
|
</button>
|
|
<button class="btn btn-warning" onclick="pauseSimulation()">
|
|
<i class="bi bi-pause"></i> Pause
|
|
</button>
|
|
<button class="btn btn-danger" onclick="stopSimulation()">
|
|
<i class="bi bi-stop"></i> Stop
|
|
</button>
|
|
<button class="btn btn-success" onclick="addRandomMessage()">
|
|
<i class="bi bi-plus"></i> Add Random Message
|
|
</button>
|
|
</div>
|
|
<div class="message-simulation">
|
|
<div class="simulation-log" id="simulationLog">
|
|
<div class="log-entry">
|
|
<span class="log-time">[System]</span>
|
|
<span class="log-status info">Simulation ready. Click "Start Simulation" to begin.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters Section -->
|
|
<div class="filter-section">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h5><i class="bi bi-funnel"></i> Filters</h5>
|
|
<div>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="refreshData()">
|
|
<i class="bi bi-arrow-clockwise"></i> Refresh
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-secondary" onclick="clearFilters()">
|
|
<i class="bi bi-x"></i> Clear Filters
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="filter-row">
|
|
<div class="filter-group">
|
|
<label class="form-label">Date Range</label>
|
|
<select class="form-select" id="dateFilter">
|
|
<option value="today">Today</option>
|
|
<option value="week">This Week</option>
|
|
<option value="month">This Month</option>
|
|
<option value="custom">Custom Range</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label class="form-label">Status</label>
|
|
<select class="form-select" id="statusFilter">
|
|
<option value="all">All Status</option>
|
|
<option value="sent">Sent</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="delivered">Delivered</option>
|
|
<option value="failed">Failed</option>
|
|
<option value="crispy">CRISPY Offers</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label class="form-label">Platform</label>
|
|
<select class="form-select" id="platformFilter">
|
|
<option value="all">All Platforms</option>
|
|
<option value="sms">SMS</option>
|
|
<option value="whatsapp">WhatsApp</option>
|
|
<option value="email">Email</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label class="form-label">Search</label>
|
|
<input type="text" class="form-control" id="searchInput" placeholder="Search by recipient or message...">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Results Table -->
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h2 class="card-title mb-0">
|
|
<i class="bi bi-table"></i> Message Results
|
|
</h2>
|
|
<div>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="exportTable()">
|
|
<i class="bi bi-download"></i> Export CSV
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>
|
|
<input type="checkbox" id="selectAllMessages" onchange="toggleSelectAllMessages()">
|
|
</th>
|
|
<th>Message ID</th>
|
|
<th>Recipient</th>
|
|
<th>Message</th>
|
|
<th>Platform</th>
|
|
<th>Status</th>
|
|
<th>Sent At</th>
|
|
<th>Delivered At</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="messageTableBody">
|
|
<!-- Messages will be populated here -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Bulk Message Actions -->
|
|
<div class="bulk-actions" id="bulkMessageActions" style="display: none;">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span id="selectedMessagesCount">0</span> messages selected
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-sm btn-primary" onclick="bulkResendMessages()">
|
|
<i class="bi bi-send"></i> Resend
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="bulkDeleteMessages()">
|
|
<i class="bi bi-trash"></i> Delete
|
|
</button>
|
|
<button class="btn btn-sm btn-secondary" onclick="clearMessageSelection()">
|
|
<i class="bi bi-x"></i> Clear Selection
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Message Detail Modal -->
|
|
<div class="modal fade" id="messageModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Message Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="messageDetails">
|
|
<!-- Message details will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Customer Edit Modal -->
|
|
<div class="modal fade" id="customerModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Edit Customer</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="editCustomerForm">
|
|
<input type="hidden" id="editCustomerId">
|
|
<div class="mb-3">
|
|
<label for="editCustomerName" class="form-label">Full Name</label>
|
|
<input type="text" class="form-control" id="editCustomerName" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editCustomerPhone" class="form-label">Phone Number</label>
|
|
<input type="tel" class="form-control" id="editCustomerPhone" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editCustomerEmail" class="form-label">Email</label>
|
|
<input type="email" class="form-control" id="editCustomerEmail">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editCustomerCity" class="form-label">City</label>
|
|
<input type="text" class="form-control" id="editCustomerCity">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editCustomerStatus" class="form-label">Status</label>
|
|
<select class="form-select" id="editCustomerStatus">
|
|
<option value="active">Active</option>
|
|
<option value="inactive">Inactive</option>
|
|
</select>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="saveEditedCustomer()">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Upload Data Modal -->
|
|
<div class="modal fade" id="uploadModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Upload Customer Data</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="file-upload-area" id="fileUploadArea">
|
|
<div class="file-upload-icon">
|
|
<i class="bi bi-cloud-upload"></i>
|
|
</div>
|
|
<div class="file-upload-text">Drag & drop your file here or click to browse</div>
|
|
<div class="file-upload-hint">Supported formats: CSV, Excel (.xlsx, .xls)</div>
|
|
<input type="file" id="fileInput" accept=".csv,.xlsx,.xls" style="display: none;">
|
|
</div>
|
|
|
|
<div class="file-info" id="fileInfo">
|
|
<h6>File Information</h6>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<strong>File Name:</strong> <span id="fileName"></span>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<strong>File Size:</strong> <span id="fileSize"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="upload-progress" id="uploadProgress">
|
|
<h6>Upload Progress</h6>
|
|
<div class="progress">
|
|
<div class="progress-bar" id="progressBar" role="progressbar" style="width: 0%"></div>
|
|
</div>
|
|
<div class="text-center mt-2">
|
|
<span id="progressText">Processing...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="upload-results" id="uploadResults">
|
|
<h6>Upload Results</h6>
|
|
<div class="result-item">
|
|
<span>Total Records:</span>
|
|
<span id="totalRecords">0</span>
|
|
</div>
|
|
<div class="result-item">
|
|
<span>Successfully Added:</span>
|
|
<span id="successCount" class="result-status success">0</span>
|
|
</div>
|
|
<div class="result-item">
|
|
<span>Duplicates Skipped:</span>
|
|
<span id="duplicateCount" class="result-status warning">0</span>
|
|
</div>
|
|
<div class="result-item">
|
|
<span>Errors:</span>
|
|
<span id="errorCount" class="result-status error">0</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
<button type="button" class="btn btn-primary" id="uploadBtn" onclick="processFile()" disabled>Upload</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notifications Modal -->
|
|
<div class="modal fade" id="notificationsModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Notifications</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="notificationsList">
|
|
<!-- Notifications will be populated here -->
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-primary" onclick="clearAllNotifications()">
|
|
Clear All
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Modal -->
|
|
<div class="modal fade" id="settingsModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Settings</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="defaultWhatsappNumber" class="form-label">Default WhatsApp Number</label>
|
|
<input type="text" class="form-control" id="defaultWhatsappNumber" value="0501882206">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Notification Preferences</label>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="soundNotifications" checked>
|
|
<label class="form-check-label" for="soundNotifications">
|
|
Enable sound notifications
|
|
</label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="desktopNotifications" checked>
|
|
<label class="form-check-label" for="desktopNotifications">
|
|
Enable desktop notifications
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Appearance</label>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="darkModeSetting">
|
|
<label class="form-check-label" for="darkModeSetting">
|
|
Dark Mode
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="refreshInterval" class="form-label">Data Refresh Interval (seconds)</label>
|
|
<select class="form-select" id="refreshInterval">
|
|
<option value="5">5 seconds</option>
|
|
<option value="10" selected>10 seconds</option>
|
|
<option value="30">30 seconds</option>
|
|
<option value="60">1 minute</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" onclick="saveSettings()">Save Settings</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toast Container -->
|
|
<div class="toast-container" id="toastContainer"></div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
// Global variables
|
|
let messages = [];
|
|
let filteredMessages = [];
|
|
let deliveryChart = null;
|
|
let statusChart = null;
|
|
let whatsappOffers = [];
|
|
let crispyOffers = [];
|
|
let customers = [];
|
|
let selectedFile = null;
|
|
let notifications = [];
|
|
let simulationInterval = null;
|
|
let scheduledOffers = [];
|
|
let selectedCustomers = new Set();
|
|
let selectedMessages = new Set();
|
|
let refreshIntervalId = null;
|
|
|
|
// Offer templates
|
|
const offerTemplates = {
|
|
crispy: `🔥 CRISPY CHICKEN - ULTIMATE OFFER 🔥
|
|
🍗 12 Crispy Chicken Pieces + 3 Garlic Sauce
|
|
Only 30 AED
|
|
⏰ Valid: Monday & Tuesday Only
|
|
🎁 Special Bonus!
|
|
Get a FREE drink with every order above 50 AED!
|
|
📞 To Order Directly: 600527488
|
|
Order Now!
|
|
⏳ Limited Time!
|
|
Order within 2 hours only!
|
|
👉 Share with friends & get extra discount!
|
|
#CrispyChicken #SpecialOffer #CrispyChickenDeal`,
|
|
|
|
special: `🎉 SPECIAL DISCOUNT FROM CRISPY CHICKEN 🎉
|
|
Get 25% OFF on your next order!
|
|
Use code: CRISPY25
|
|
Valid for all menu items
|
|
📞 Call now: 600527488
|
|
Offer valid until the end of the month!
|
|
Don't miss out on this amazing deal!
|
|
#CrispyChicken #SpecialDiscount #CrispyDeals`,
|
|
|
|
new: `🆕 NEW PRODUCT ALERT FROM CRISPY CHICKEN 🆕
|
|
Introducing our Spicy Crispy Burger!
|
|
🌶️ Extra spicy, extra crispy!
|
|
🧀 Double cheese patty
|
|
🍔 Fresh vegetables
|
|
Special launch price: 35 AED only!
|
|
📞 Order now: 600527488
|
|
Limited time offer!
|
|
#CrispyChicken #NewProduct #SpicyBurger`,
|
|
|
|
custom: `Customize your own offer message here.
|
|
Make sure to include "CRISPY CHICKEN" in your message.`
|
|
};
|
|
|
|
// Initialize the application
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadSettings();
|
|
loadCustomersFromStorage();
|
|
loadMessagesFromStorage();
|
|
loadScheduledOffersFromStorage();
|
|
generateSampleData();
|
|
generateSampleCustomers();
|
|
setupEventListeners();
|
|
updateStatistics();
|
|
updateCharts();
|
|
updateTable();
|
|
updateWhatsappStats();
|
|
updateCrispyStats();
|
|
updateCustomerCount();
|
|
updateCustomerList();
|
|
updateOfferPreview();
|
|
updateRecentActivity();
|
|
updateTodayStats();
|
|
setupAutoRefresh();
|
|
checkScheduledOffers();
|
|
|
|
// Add sample notifications
|
|
addNotification('info', 'System started successfully');
|
|
addNotification('success', '3 messages delivered successfully');
|
|
addNotification('warning', '1 message failed to deliver');
|
|
|
|
// Update notification badge
|
|
updateNotificationBadge();
|
|
});
|
|
|
|
function generateSampleData() {
|
|
// Only generate sample data if no messages exist
|
|
if (messages.length === 0) {
|
|
const sampleMessages = [
|
|
{
|
|
id: 'MSG001',
|
|
recipient: 'John Smith',
|
|
phone: '+971501234567',
|
|
message: 'Your order has been delivered successfully',
|
|
platform: 'SMS',
|
|
status: 'delivered',
|
|
sentAt: new Date(Date.now() - 1000 * 60 * 5),
|
|
deliveredAt: new Date(Date.now() - 1000 * 60 * 3),
|
|
retryCount: 0
|
|
},
|
|
{
|
|
id: 'MSG002',
|
|
recipient: 'Alice Johnson',
|
|
phone: '+971551234568',
|
|
message: 'Reminder: Your appointment is tomorrow at 2 PM',
|
|
platform: 'WhatsApp',
|
|
status: 'sent',
|
|
sentAt: new Date(Date.now() - 1000 * 60 * 10),
|
|
deliveredAt: null,
|
|
retryCount: 0
|
|
},
|
|
{
|
|
id: 'MSG003',
|
|
recipient: 'Bob Wilson',
|
|
phone: '+971561234569',
|
|
message: 'Payment received. Thank you for your purchase',
|
|
platform: 'SMS',
|
|
status: 'pending',
|
|
sentAt: new Date(Date.now() - 1000 * 60 * 15),
|
|
deliveredAt: null,
|
|
retryCount: 0
|
|
},
|
|
{
|
|
id: 'MSG004',
|
|
recipient: 'Emma Davis',
|
|
phone: '+971581234570',
|
|
message: 'Your support ticket has been resolved',
|
|
platform: 'Email',
|
|
status: 'failed',
|
|
sentAt: new Date(Date.now() - 1000 * 60 * 20),
|
|
deliveredAt: null,
|
|
retryCount: 2
|
|
},
|
|
{
|
|
id: 'MSG005',
|
|
recipient: 'Michael Brown',
|
|
phone: '+971501234571',
|
|
message: 'Welcome to our service! Here\'s your discount code',
|
|
platform: 'WhatsApp',
|
|
status: 'delivered',
|
|
sentAt: new Date(Date.now() - 1000 * 60 * 25),
|
|
deliveredAt: new Date(Date.now() - 1000 * 60 * 23),
|
|
retryCount: 0
|
|
},
|
|
{
|
|
id: 'MSG006',
|
|
recipient: 'Sarah Johnson',
|
|
phone: '+971551234572',
|
|
message: 'Your order is out for delivery',
|
|
platform: 'SMS',
|
|
status: 'sent',
|
|
sentAt: new Date(Date.now() - 1000 * 60 * 30),
|
|
deliveredAt: null,
|
|
retryCount: 0
|
|
},
|
|
{
|
|
id: 'MSG007',
|
|
recipient: 'David Wilson',
|
|
phone: '+971561234573',
|
|
message: 'Payment failed. Please update your payment method',
|
|
platform: 'SMS',
|
|
status: 'failed',
|
|
sentAt: new Date(Date.now() - 1000 * 60 * 35),
|
|
deliveredAt: null,
|
|
retryCount: 1
|
|
},
|
|
{
|
|
id: 'MSG008',
|
|
recipient: 'Lisa Davis',
|
|
phone: '+971581234574',
|
|
message: 'Your feedback is important to us',
|
|
platform: 'Email',
|
|
status: 'pending',
|
|
sentAt: new Date(Date.now() - 1000 * 60 * 40),
|
|
deliveredAt: null,
|
|
retryCount: 0
|
|
}
|
|
];
|
|
|
|
messages = sampleMessages;
|
|
saveMessagesToStorage();
|
|
}
|
|
|
|
filteredMessages = [...messages];
|
|
}
|
|
|
|
function generateSampleCustomers() {
|
|
// Only generate sample customers if none exist
|
|
if (customers.length === 0) {
|
|
const sampleCustomers = [
|
|
{
|
|
id: 1,
|
|
name: 'John Smith',
|
|
phone: '+971501234567',
|
|
email: 'john.smith@example.com',
|
|
city: 'Dubai',
|
|
status: 'active',
|
|
createdAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000)
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Alice Johnson',
|
|
phone: '+971551234568',
|
|
email: 'alice.johnson@example.com',
|
|
city: 'Abu Dhabi',
|
|
status: 'active',
|
|
createdAt: new Date(Date.now() - 8 * 24 * 60 * 60 * 1000)
|
|
},
|
|
{
|
|
id: 3,
|
|
name: 'Bob Wilson',
|
|
phone: '+971561234569',
|
|
email: 'bob.wilson@example.com',
|
|
city: 'Sharjah',
|
|
status: 'active',
|
|
createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000)
|
|
},
|
|
{
|
|
id: 4,
|
|
name: 'Emma Davis',
|
|
phone: '+971581234570',
|
|
email: 'emma.davis@example.com',
|
|
city: 'Dubai',
|
|
status: 'inactive',
|
|
createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000)
|
|
},
|
|
{
|
|
id: 5,
|
|
name: 'Michael Brown',
|
|
phone: '+971501234571',
|
|
email: 'michael.brown@example.com',
|
|
city: 'Ajman',
|
|
status: 'active',
|
|
createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000)
|
|
}
|
|
];
|
|
|
|
customers = sampleCustomers;
|
|
saveCustomersToStorage();
|
|
}
|
|
}
|
|
|
|
function setupEventListeners() {
|
|
// Filters
|
|
document.getElementById('dateFilter').addEventListener('change', applyFilters);
|
|
document.getElementById('statusFilter').addEventListener('change', applyFilters);
|
|
document.getElementById('platformFilter').addEventListener('change', applyFilters);
|
|
document.getElementById('searchInput').addEventListener('input', applyFilters);
|
|
|
|
// WhatsApp Offer Form
|
|
document.getElementById('whatsappOfferForm').addEventListener('submit', sendWhatsappOffer);
|
|
|
|
// Offer message textarea
|
|
document.getElementById('offerMessage').addEventListener('input', updateOfferPreview);
|
|
|
|
// Add Customer Form
|
|
document.getElementById('addCustomerForm').addEventListener('submit', addCustomer);
|
|
|
|
// Customer search
|
|
document.getElementById('customerSearchInput').addEventListener('input', filterCustomers);
|
|
|
|
// Customer filter buttons
|
|
document.querySelectorAll('.customer-filter-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
document.querySelectorAll('.customer-filter-btn').forEach(b => b.classList.remove('active'));
|
|
this.classList.add('active');
|
|
filterCustomers();
|
|
});
|
|
});
|
|
|
|
// File Upload
|
|
const fileUploadArea = document.getElementById('fileUploadArea');
|
|
const fileInput = document.getElementById('fileInput');
|
|
|
|
fileUploadArea.addEventListener('click', () => fileInput.click());
|
|
fileUploadArea.addEventListener('dragover', handleDragOver);
|
|
fileUploadArea.addEventListener('dragleave', handleDragLeave);
|
|
fileUploadArea.addEventListener('drop', handleDrop);
|
|
fileInput.addEventListener('change', handleFileSelect);
|
|
|
|
// Schedule offer checkbox
|
|
document.getElementById('scheduleOffer').addEventListener('change', function() {
|
|
const scheduledOffersDiv = document.getElementById('scheduledOffers');
|
|
if (this.checked) {
|
|
scheduledOffersDiv.style.display = 'block';
|
|
updateScheduledOffersList();
|
|
} else {
|
|
scheduledOffersDiv.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateStatistics() {
|
|
const sent = messages.filter(m => m.status === 'sent').length;
|
|
const pending = messages.filter(m => m.status === 'pending').length;
|
|
const delivered = messages.filter(m => m.status === 'delivered').length;
|
|
const failed = messages.filter(m => m.status === 'failed').length;
|
|
const crispy = messages.filter(m => m.status === 'crispy').length;
|
|
const activeCustomers = customers.filter(c => c.status === 'active').length;
|
|
|
|
document.getElementById('totalSent').textContent = sent;
|
|
document.getElementById('totalPending').textContent = pending;
|
|
document.getElementById('totalDelivered').textContent = delivered;
|
|
document.getElementById('totalFailed').textContent = failed;
|
|
document.getElementById('totalCrispy').textContent = crispy;
|
|
document.getElementById('totalCustomers').textContent = activeCustomers;
|
|
}
|
|
|
|
function updateTodayStats() {
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
const tomorrow = new Date(today);
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
const todayMessages = messages.filter(m => {
|
|
const sentDate = new Date(m.sentAt);
|
|
return sentDate >= today && sentDate < tomorrow;
|
|
});
|
|
|
|
const todaySent = todayMessages.filter(m => m.status === 'sent' || m.status === 'delivered' || m.status === 'failed').length;
|
|
const todayDelivered = todayMessages.filter(m => m.status === 'delivered').length;
|
|
const todayFailed = todayMessages.filter(m => m.status === 'failed').length;
|
|
const todayOffers = todayMessages.filter(m => m.status === 'crispy').length;
|
|
|
|
document.getElementById('todaySent').textContent = todaySent;
|
|
document.getElementById('todayDelivered').textContent = todayDelivered;
|
|
document.getElementById('todayFailed').textContent = todayFailed;
|
|
document.getElementById('todayOffers').textContent = todayOffers;
|
|
}
|
|
|
|
function updateWhatsappStats() {
|
|
const sent = whatsappOffers.filter(o => o.status === 'sent').length;
|
|
const delivered = whatsappOffers.filter(o => o.status === 'delivered').length;
|
|
const failed = whatsappOffers.filter(o => o.status === 'failed').length;
|
|
|
|
document.getElementById('totalSentOffers').textContent = sent;
|
|
document.getElementById('totalDeliveredOffers').textContent = delivered;
|
|
document.getElementById('totalFailedOffers').textContent = failed;
|
|
}
|
|
|
|
function updateCrispyStats() {
|
|
const sent = crispyOffers.filter(o => o.status === 'sent').length;
|
|
const delivered = crispyOffers.filter(o => o.status === 'delivered').length;
|
|
const failed = crispyOffers.filter(o => o.status === 'failed').length;
|
|
|
|
// Update crispy stats in the main dashboard
|
|
const totalCrispy = sent + delivered + failed;
|
|
document.getElementById('totalCrispy').textContent = totalCrispy;
|
|
}
|
|
|
|
function updateCustomerCount() {
|
|
const activeCustomers = customers.filter(c => c.status === 'active').length;
|
|
document.getElementById('customerCount').value = activeCustomers;
|
|
}
|
|
|
|
function updateOfferPreview() {
|
|
const message = document.getElementById('offerMessage').value;
|
|
document.getElementById('offerPreview').textContent = message;
|
|
}
|
|
|
|
function updateCharts() {
|
|
// Delivery Chart
|
|
const deliveryCtx = document.getElementById('deliveryChart').getContext('2d');
|
|
|
|
if (deliveryChart) {
|
|
deliveryChart.destroy();
|
|
}
|
|
|
|
const hourlyData = getHourlyDeliveryData();
|
|
|
|
deliveryChart = new Chart(deliveryCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: hourlyData.labels,
|
|
datasets: [{
|
|
label: 'Messages Sent',
|
|
data: hourlyData.sent,
|
|
borderColor: '#2563eb',
|
|
backgroundColor: 'rgba(37, 99, 235, 0.1)',
|
|
tension: 0.4
|
|
}, {
|
|
label: 'Messages Delivered',
|
|
data: hourlyData.delivered,
|
|
borderColor: '#10b981',
|
|
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom'
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Status Chart
|
|
const statusCtx = document.getElementById('statusChart').getContext('2d');
|
|
|
|
if (statusChart) {
|
|
statusChart.destroy();
|
|
}
|
|
|
|
const statusCounts = {
|
|
sent: messages.filter(m => m.status === 'sent').length,
|
|
pending: messages.filter(m => m.status === 'pending').length,
|
|
delivered: messages.filter(m => m.status === 'delivered').length,
|
|
failed: messages.filter(m => m.status === 'failed').length,
|
|
crispy: messages.filter(m => m.status === 'crispy').length
|
|
};
|
|
|
|
statusChart = new Chart(statusCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: ['Sent', 'Pending', 'Delivered', 'Failed', 'CRISPY Offers'],
|
|
datasets: [{
|
|
data: [statusCounts.sent, statusCounts.pending, statusCounts.delivered, statusCounts.failed, statusCounts.crispy],
|
|
backgroundColor: ['#2563eb', '#f59e0b', '#10b981', '#ef4444', '#ff6b6b']
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function getHourlyDeliveryData() {
|
|
const hours = [];
|
|
const sent = [];
|
|
const delivered = [];
|
|
|
|
for (let i = 0; i < 24; i++) {
|
|
hours.push(`${i}:00`);
|
|
const hourMessages = messages.filter(m => {
|
|
const messageHour = new Date(m.sentAt || m.deliveredAt).getHours();
|
|
return messageHour === i;
|
|
});
|
|
|
|
sent.push(hourMessages.filter(m => m.status === 'sent' || m.status === 'delivered' || m.status === 'failed' || m.status === 'crispy').length);
|
|
delivered.push(hourMessages.filter(m => m.status === 'delivered').length);
|
|
}
|
|
|
|
return { labels: hours, sent, delivered };
|
|
}
|
|
|
|
function applyFilters() {
|
|
const dateFilter = document.getElementById('dateFilter').value;
|
|
const statusFilter = document.getElementById('statusFilter').value;
|
|
const platformFilter = document.getElementById('platformFilter').value;
|
|
const searchInput = document.getElementById('searchInput').value.toLowerCase();
|
|
|
|
filteredMessages = messages.filter(message => {
|
|
// Date filter
|
|
let passesDateFilter = true;
|
|
if (dateFilter !== 'all') {
|
|
const messageDate = new Date(message.sentAt);
|
|
const now = new Date();
|
|
|
|
if (dateFilter === 'today') {
|
|
passesDateFilter = messageDate.toDateString() === now.toDateString();
|
|
} else if (dateFilter === 'week') {
|
|
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
passesDateFilter = messageDate >= weekAgo;
|
|
} else if (dateFilter === 'month') {
|
|
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
passesDateFilter = messageDate >= monthAgo;
|
|
}
|
|
}
|
|
|
|
// Status filter
|
|
const passesStatusFilter = statusFilter === 'all' || message.status === statusFilter;
|
|
|
|
// Platform filter
|
|
const passesPlatformFilter = platformFilter === 'all' || message.platform === platformFilter;
|
|
|
|
// Search filter
|
|
const passesSearchFilter = searchInput === '' ||
|
|
message.recipient.toLowerCase().includes(searchInput) ||
|
|
message.message.toLowerCase().includes(searchInput) ||
|
|
message.id.toLowerCase().includes(searchInput);
|
|
|
|
return passesDateFilter && passesStatusFilter && passesPlatformFilter && passesSearchFilter;
|
|
});
|
|
|
|
updateTable();
|
|
updateStatistics();
|
|
updateCharts();
|
|
}
|
|
|
|
function updateTable() {
|
|
const tableBody = document.getElementById('messageTableBody');
|
|
tableBody.innerHTML = '';
|
|
|
|
if (filteredMessages.length === 0) {
|
|
const emptyRow = document.createElement('tr');
|
|
emptyRow.innerHTML = `
|
|
<td colspan="9" class="text-center py-4">
|
|
<div class="text-muted">No messages found matching your criteria</div>
|
|
</td>
|
|
`;
|
|
tableBody.appendChild(emptyRow);
|
|
return;
|
|
}
|
|
|
|
filteredMessages.forEach(message => {
|
|
const row = document.createElement('tr');
|
|
|
|
// Format dates
|
|
const sentAt = message.sentAt ? new Date(message.sentAt).toLocaleString() : 'N/A';
|
|
const deliveredAt = message.deliveredAt ? new Date(message.deliveredAt).toLocaleString() : 'N/A';
|
|
|
|
// Create status badge
|
|
let statusBadge = '';
|
|
switch (message.status) {
|
|
case 'sent':
|
|
statusBadge = '<span class="status-badge sent"><i class="bi bi-send"></i> Sent</span>';
|
|
break;
|
|
case 'pending':
|
|
statusBadge = '<span class="status-badge pending"><i class="bi bi-clock"></i> Pending</span>';
|
|
break;
|
|
case 'delivered':
|
|
statusBadge = '<span class="status-badge delivered"><i class="bi bi-check-circle"></i> Delivered</span>';
|
|
break;
|
|
case 'failed':
|
|
statusBadge = '<span class="status-badge failed"><i class="bi bi-exclamation-circle"></i> Failed</span>';
|
|
break;
|
|
case 'crispy':
|
|
statusBadge = '<span class="status-badge crispy"><i class="bi bi-fire"></i> CRISPY Offer</span>';
|
|
break;
|
|
}
|
|
|
|
// Create platform icon
|
|
let platformIcon = '';
|
|
switch (message.platform) {
|
|
case 'SMS':
|
|
platformIcon = '<i class="bi bi-chat-text"></i> SMS';
|
|
break;
|
|
case 'WhatsApp':
|
|
platformIcon = '<i class="bi bi-whatsapp"></i> WhatsApp';
|
|
break;
|
|
case 'Email':
|
|
platformIcon = '<i class="bi bi-envelope"></i> Email';
|
|
break;
|
|
}
|
|
|
|
row.innerHTML = `
|
|
<td>
|
|
<input type="checkbox" class="message-checkbox" data-id="${message.id}" onchange="toggleMessageSelection('${message.id}')">
|
|
</td>
|
|
<td>${message.id}</td>
|
|
<td>${message.recipient}</td>
|
|
<td>
|
|
<div class="message-preview">
|
|
<div class="message-preview-body">${message.message.substring(0, 100)}${message.message.length > 100 ? '...' : ''}</div>
|
|
</div>
|
|
</td>
|
|
<td>${platformIcon}</td>
|
|
<td>${statusBadge}</td>
|
|
<td>${sentAt}</td>
|
|
<td>${deliveredAt}</td>
|
|
<td>
|
|
<div class="d-flex gap-2">
|
|
<button class="btn btn-sm btn-outline-primary btn-icon" onclick="viewMessageDetails('${message.id}')" title="View Details">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
${message.status === 'failed' ? `
|
|
<button class="btn btn-sm btn-outline-warning btn-icon" onclick="retryMessage('${message.id}')" title="Retry">
|
|
<i class="bi bi-arrow-clockwise"></i>
|
|
</button>
|
|
` : ''}
|
|
</div>
|
|
</td>
|
|
`;
|
|
|
|
tableBody.appendChild(row);
|
|
});
|
|
|
|
// Update select all checkbox
|
|
updateSelectAllCheckbox();
|
|
}
|
|
|
|
function viewMessageDetails(messageId) {
|
|
const message = messages.find(m => m.id === messageId);
|
|
if (!message) return;
|
|
|
|
const modal = new bootstrap.Modal(document.getElementById('messageModal'));
|
|
const detailsContainer = document.getElementById('messageDetails');
|
|
|
|
// Format dates
|
|
const sentAt = message.sentAt ? new Date(message.sentAt).toLocaleString() : 'N/A';
|
|
const deliveredAt = message.deliveredAt ? new Date(message.deliveredAt).toLocaleString() : 'N/A';
|
|
|
|
// Create status badge
|
|
let statusBadge = '';
|
|
switch (message.status) {
|
|
case 'sent':
|
|
statusBadge = '<span class="status-badge sent"><i class="bi bi-send"></i> Sent</span>';
|
|
break;
|
|
case 'pending':
|
|
statusBadge = '<span class="status-badge pending"><i class="bi bi-clock"></i> Pending</span>';
|
|
break;
|
|
case 'delivered':
|
|
statusBadge = '<span class="status-badge delivered"><i class="bi bi-check-circle"></i> Delivered</span>';
|
|
break;
|
|
case 'failed':
|
|
statusBadge = '<span class="status-badge failed"><i class="bi bi-exclamation-circle"></i> Failed</span>';
|
|
break;
|
|
case 'crispy':
|
|
statusBadge = '<span class="status-badge crispy"><i class="bi bi-fire"></i> CRISPY Offer</span>';
|
|
break;
|
|
}
|
|
|
|
// Create timeline
|
|
let timelineHtml = `
|
|
<div class="timeline">
|
|
<div class="timeline-item sent">
|
|
<div class="timeline-content">
|
|
<div class="timeline-time">${sentAt}</div>
|
|
<div class="timeline-message">Message sent</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
if (message.deliveredAt) {
|
|
timelineHtml += `
|
|
<div class="timeline-item delivered">
|
|
<div class="timeline-content">
|
|
<div class="timeline-time">${deliveredAt}</div>
|
|
<div class="timeline-message">Message delivered</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (message.status === 'failed') {
|
|
timelineHtml += `
|
|
<div class="timeline-item failed">
|
|
<div class="timeline-content">
|
|
<div class="timeline-time">${sentAt}</div>
|
|
<div class="timeline-message">Delivery failed</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (message.status === 'crispy') {
|
|
timelineHtml += `
|
|
<div class="timeline-item crispy">
|
|
<div class="timeline-content">
|
|
<div class="timeline-time">${sentAt}</div>
|
|
<div class="timeline-message">CRISPY CHICKEN offer sent</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
if (message.deliveredAt) {
|
|
timelineHtml += `
|
|
<div class="timeline-item delivered">
|
|
<div class="timeline-content">
|
|
<div class="timeline-time">${deliveredAt}</div>
|
|
<div class="timeline-message">Offer delivered</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
timelineHtml += '</div>';
|
|
|
|
detailsContainer.innerHTML = `
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<h6>Message Information</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Message ID:</strong></td>
|
|
<td>${message.id}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Recipient:</strong></td>
|
|
<td>${message.recipient}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Phone/Email:</strong></td>
|
|
<td>${message.phone || 'N/A'}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Platform:</strong></td>
|
|
<td>${message.platform}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Status:</strong></td>
|
|
<td>${statusBadge}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Retry Count:</strong></td>
|
|
<td>${message.retryCount || 0}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Timestamps</h6>
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<td><strong>Sent At:</strong></td>
|
|
<td>${sentAt}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Delivered At:</strong></td>
|
|
<td>${deliveredAt}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="mb-4">
|
|
<h6>Message Content</h6>
|
|
<div class="message-preview">
|
|
<div class="message-preview-body" style="white-space: pre-wrap;">${message.message}</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h6>Delivery Timeline</h6>
|
|
${timelineHtml}
|
|
</div>
|
|
`;
|
|
|
|
modal.show();
|
|
}
|
|
|
|
function retryMessage(messageId) {
|
|
const messageIndex = messages.findIndex(m => m.id === messageId);
|
|
if (messageIndex === -1) return;
|
|
|
|
// Update message status and retry count
|
|
messages[messageIndex].status = 'pending';
|
|
messages[messageIndex].retryCount = (messages[messageIndex].retryCount || 0) + 1;
|
|
messages[messageIndex].sentAt = new Date();
|
|
|
|
// Show success toast
|
|
showToast('Message retry initiated', 'success');
|
|
|
|
// Apply filters to update the view
|
|
applyFilters();
|
|
|
|
// Simulate delivery after a delay
|
|
setTimeout(() => {
|
|
const success = Math.random() > 0.3; // 70% success rate for retries
|
|
|
|
if (success) {
|
|
messages[messageIndex].status = 'delivered';
|
|
messages[messageIndex].deliveredAt = new Date();
|
|
showToast('Message delivered successfully', 'success');
|
|
addNotification('success', `Message ${messageId} delivered successfully`);
|
|
} else {
|
|
messages[messageIndex].status = 'failed';
|
|
showToast('Message delivery failed again', 'error');
|
|
addNotification('error', `Message ${messageId} failed to deliver`);
|
|
}
|
|
|
|
applyFilters();
|
|
updateRecentActivity();
|
|
}, 3000);
|
|
}
|
|
|
|
function refreshData() {
|
|
showToast('Refreshing data...', 'info');
|
|
|
|
// Simulate data refresh
|
|
setTimeout(() => {
|
|
// Add some new messages
|
|
const newMessage = {
|
|
id: 'MSG' + String(messages.length + 1).padStart(3, '0'),
|
|
recipient: 'New User',
|
|
phone: '+9715012345' + String(Math.floor(Math.random() * 1000)).padStart(3, '0'),
|
|
message: 'This is a new message',
|
|
platform: ['SMS', 'WhatsApp', 'Email'][Math.floor(Math.random() * 3)],
|
|
status: ['sent', 'pending', 'delivered', 'failed'][Math.floor(Math.random() * 4)],
|
|
sentAt: new Date(),
|
|
deliveredAt: Math.random() > 0.5 ? new Date(Date.now() - 1000 * 60 * Math.floor(Math.random() * 10)) : null,
|
|
retryCount: 0
|
|
};
|
|
|
|
messages.unshift(newMessage);
|
|
saveMessagesToStorage();
|
|
applyFilters();
|
|
updateCustomerCount();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
showToast('Data refreshed successfully', 'success');
|
|
addNotification('info', 'Data refreshed successfully');
|
|
}, 1000);
|
|
}
|
|
|
|
function clearFilters() {
|
|
document.getElementById('dateFilter').value = 'today';
|
|
document.getElementById('statusFilter').value = 'all';
|
|
document.getElementById('platformFilter').value = 'all';
|
|
document.getElementById('searchInput').value = '';
|
|
|
|
applyFilters();
|
|
showToast('Filters cleared', 'info');
|
|
}
|
|
|
|
function exportReport() {
|
|
showToast('Generating report...', 'info');
|
|
|
|
// Simulate report generation
|
|
setTimeout(() => {
|
|
// Create a CSV string
|
|
let csv = 'Message ID,Recipient,Platform,Status,Sent At,Delivered At\n';
|
|
|
|
filteredMessages.forEach(message => {
|
|
const sentAt = message.sentAt ? new Date(message.sentAt).toLocaleString() : 'N/A';
|
|
const deliveredAt = message.deliveredAt ? new Date(message.deliveredAt).toLocaleString() : 'N/A';
|
|
|
|
csv += `${message.id},${message.recipient},${message.platform},${message.status},${sentAt},${deliveredAt}\n`;
|
|
});
|
|
|
|
// Create a download link
|
|
const blob = new Blob([csv], { type: 'text/csv' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.setAttribute('hidden', '');
|
|
a.setAttribute('href', url);
|
|
a.setAttribute('download', 'message_report.csv');
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
|
|
showToast('Report exported successfully', 'success');
|
|
addNotification('success', 'Report exported successfully');
|
|
}, 1000);
|
|
}
|
|
|
|
function exportTable() {
|
|
exportReport(); // Same functionality as exportReport
|
|
}
|
|
|
|
function showSettings() {
|
|
const modal = new bootstrap.Modal(document.getElementById('settingsModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function saveSettings() {
|
|
const settings = {
|
|
defaultWhatsappNumber: document.getElementById('defaultWhatsappNumber').value,
|
|
soundNotifications: document.getElementById('soundNotifications').checked,
|
|
desktopNotifications: document.getElementById('desktopNotifications').checked,
|
|
darkMode: document.getElementById('darkModeSetting').checked,
|
|
refreshInterval: parseInt(document.getElementById('refreshInterval').value)
|
|
};
|
|
|
|
localStorage.setItem('crispySettings', JSON.stringify(settings));
|
|
|
|
// Apply dark mode if enabled
|
|
if (settings.darkMode) {
|
|
document.body.classList.add('dark-mode');
|
|
document.getElementById('darkModeIcon').classList.replace('bi-moon-stars', 'bi-sun');
|
|
} else {
|
|
document.body.classList.remove('dark-mode');
|
|
document.getElementById('darkModeIcon').classList.replace('bi-sun', 'bi-moon-stars');
|
|
}
|
|
|
|
// Update WhatsApp number
|
|
document.getElementById('whatsappNumber').value = settings.defaultWhatsappNumber;
|
|
|
|
// Setup auto refresh with new interval
|
|
setupAutoRefresh();
|
|
|
|
// Close modal
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('settingsModal'));
|
|
modal.hide();
|
|
|
|
showToast('Settings saved successfully', 'success');
|
|
addNotification('success', 'Settings saved successfully');
|
|
}
|
|
|
|
function loadSettings() {
|
|
const settings = JSON.parse(localStorage.getItem('crispySettings')) || {};
|
|
|
|
// Apply settings
|
|
document.getElementById('defaultWhatsappNumber').value = settings.defaultWhatsappNumber || '0501882206';
|
|
document.getElementById('soundNotifications').checked = settings.soundNotifications !== false;
|
|
document.getElementById('desktopNotifications').checked = settings.desktopNotifications !== false;
|
|
document.getElementById('darkModeSetting').checked = settings.darkMode || false;
|
|
document.getElementById('refreshInterval').value = settings.refreshInterval || 10;
|
|
|
|
// Apply dark mode if enabled
|
|
if (settings.darkMode) {
|
|
document.body.classList.add('dark-mode');
|
|
document.getElementById('darkModeIcon').classList.replace('bi-moon-stars', 'bi-sun');
|
|
}
|
|
|
|
// Update WhatsApp number
|
|
document.getElementById('whatsappNumber').value = settings.defaultWhatsappNumber || '0501882206';
|
|
}
|
|
|
|
function toggleDarkMode() {
|
|
const isDarkMode = document.body.classList.toggle('dark-mode');
|
|
const icon = document.getElementById('darkModeIcon');
|
|
|
|
if (isDarkMode) {
|
|
icon.classList.replace('bi-moon-stars', 'bi-sun');
|
|
} else {
|
|
icon.classList.replace('bi-sun', 'bi-moon-stars');
|
|
}
|
|
|
|
// Update settings
|
|
const settings = JSON.parse(localStorage.getItem('crispySettings')) || {};
|
|
settings.darkMode = isDarkMode;
|
|
localStorage.setItem('crispySettings', JSON.stringify(settings));
|
|
|
|
showToast(`Dark mode ${isDarkMode ? 'enabled' : 'disabled'}`, 'info');
|
|
}
|
|
|
|
function showToast(message, type = 'info') {
|
|
const toastContainer = document.getElementById('toastContainer');
|
|
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
|
|
let icon = '';
|
|
switch (type) {
|
|
case 'success':
|
|
icon = '<i class="bi bi-check-circle-fill"></i>';
|
|
break;
|
|
case 'error':
|
|
icon = '<i class="bi bi-exclamation-circle-fill"></i>';
|
|
break;
|
|
case 'warning':
|
|
icon = '<i class="bi bi-exclamation-triangle-fill"></i>';
|
|
break;
|
|
case 'whatsapp':
|
|
icon = '<i class="bi bi-whatsapp"></i>';
|
|
break;
|
|
case 'crispy':
|
|
icon = '<i class="bi bi-fire"></i>';
|
|
break;
|
|
case 'info':
|
|
default:
|
|
icon = '<i class="bi bi-info-circle-fill"></i>';
|
|
break;
|
|
}
|
|
|
|
toast.innerHTML = `
|
|
${icon}
|
|
<div>${message}</div>
|
|
`;
|
|
|
|
toastContainer.appendChild(toast);
|
|
|
|
// Auto remove after 3 seconds
|
|
setTimeout(() => {
|
|
toast.style.opacity = '0';
|
|
toast.style.transform = 'translateX(100%)';
|
|
|
|
setTimeout(() => {
|
|
toastContainer.removeChild(toast);
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// WhatsApp Offer System Functions
|
|
function sendWhatsappOffer(e) {
|
|
e.preventDefault();
|
|
|
|
const whatsappNumber = document.getElementById('whatsappNumber').value;
|
|
const offerMessage = document.getElementById('offerMessage').value;
|
|
const scheduleOffer = document.getElementById('scheduleOffer').checked;
|
|
|
|
// Ensure the message contains "CRISPY CHICKEN"
|
|
if (!offerMessage.includes('CRISPY CHICKEN')) {
|
|
showToast('The offer message must include "CRISPY CHICKEN"', 'warning');
|
|
return;
|
|
}
|
|
|
|
const activeCustomers = customers.filter(c => c.status === 'active');
|
|
|
|
if (activeCustomers.length === 0) {
|
|
showToast('No active customers found', 'warning');
|
|
return;
|
|
}
|
|
|
|
if (scheduleOffer) {
|
|
const scheduleDate = document.getElementById('scheduleDate').value;
|
|
const scheduleTime = document.getElementById('scheduleTime').value;
|
|
|
|
if (!scheduleDate || !scheduleTime) {
|
|
showToast('Please select both date and time for scheduling', 'warning');
|
|
return;
|
|
}
|
|
|
|
const scheduledDateTime = new Date(`${scheduleDate}T${scheduleTime}`);
|
|
|
|
if (scheduledDateTime <= new Date()) {
|
|
showToast('Scheduled time must be in the future', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Create scheduled offer
|
|
const scheduledOffer = {
|
|
id: 'SCH' + String(scheduledOffers.length + 1).padStart(3, '0'),
|
|
message: offerMessage,
|
|
scheduledAt: scheduledDateTime,
|
|
whatsappNumber: whatsappNumber,
|
|
status: 'scheduled'
|
|
};
|
|
|
|
scheduledOffers.push(scheduledOffer);
|
|
saveScheduledOffersToStorage();
|
|
|
|
showToast(`Offer scheduled for ${scheduledDateTime.toLocaleString()}`, 'success');
|
|
addNotification('success', `Offer scheduled for ${scheduledDateTime.toLocaleString()}`);
|
|
|
|
// Update scheduled offers list
|
|
updateScheduledOffersList();
|
|
|
|
return;
|
|
}
|
|
|
|
// Create new WhatsApp offers for each active customer
|
|
const newOffers = activeCustomers.map((customer, index) => {
|
|
return {
|
|
id: 'CRS' + String(crispyOffers.length + index + 1).padStart(3, '0'),
|
|
recipient: customer.name,
|
|
phone: customer.phone,
|
|
message: offerMessage,
|
|
platform: 'WhatsApp',
|
|
status: 'pending',
|
|
sentAt: new Date(),
|
|
deliveredAt: null,
|
|
retryCount: 0,
|
|
whatsappNumber: whatsappNumber
|
|
};
|
|
});
|
|
|
|
// Add the new offers to the crispyOffers array
|
|
crispyOffers = [...newOffers, ...crispyOffers];
|
|
|
|
// Also add to the messages array for tracking
|
|
messages = [...newOffers, ...messages];
|
|
saveMessagesToStorage();
|
|
|
|
// Update the UI
|
|
updateCustomerCount();
|
|
updateWhatsappStats();
|
|
updateCrispyStats();
|
|
applyFilters();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
|
|
showToast(`Sending CRISPY CHICKEN offer to ${activeCustomers.length} customers...`, 'crispy');
|
|
addNotification('crispy', `Sending CRISPY CHICKEN offer to ${activeCustomers.length} customers`);
|
|
|
|
// Simulate sending process
|
|
setTimeout(() => {
|
|
// Update status to 'crispy'
|
|
newOffers.forEach(offer => {
|
|
const index = messages.findIndex(m => m.id === offer.id);
|
|
if (index !== -1) {
|
|
messages[index].status = 'crispy';
|
|
}
|
|
|
|
const offerIndex = crispyOffers.findIndex(o => o.id === offer.id);
|
|
if (offerIndex !== -1) {
|
|
crispyOffers[offerIndex].status = 'sent';
|
|
}
|
|
});
|
|
|
|
saveMessagesToStorage();
|
|
updateWhatsappStats();
|
|
updateCrispyStats();
|
|
applyFilters();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
showToast('CRISPY CHICKEN offers sent successfully', 'crispy');
|
|
addNotification('success', 'CRISPY CHICKEN offers sent successfully');
|
|
|
|
// Simulate delivery after a delay
|
|
setTimeout(() => {
|
|
newOffers.forEach(offer => {
|
|
const index = messages.findIndex(m => m.id === offer.id);
|
|
if (index !== -1) {
|
|
// 90% success rate for delivery
|
|
const success = Math.random() > 0.1;
|
|
if (success) {
|
|
messages[index].status = 'delivered';
|
|
messages[index].deliveredAt = new Date();
|
|
|
|
const offerIndex = crispyOffers.findIndex(o => o.id === offer.id);
|
|
if (offerIndex !== -1) {
|
|
crispyOffers[offerIndex].status = 'delivered';
|
|
crispyOffers[offerIndex].deliveredAt = new Date();
|
|
}
|
|
} else {
|
|
messages[index].status = 'failed';
|
|
|
|
const offerIndex = crispyOffers.findIndex(o => o.id === offer.id);
|
|
if (offerIndex !== -1) {
|
|
crispyOffers[offerIndex].status = 'failed';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
saveMessagesToStorage();
|
|
updateWhatsappStats();
|
|
updateCrispyStats();
|
|
applyFilters();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
showToast('CRISPY CHICKEN offer delivery process completed', 'crispy');
|
|
addNotification('info', 'CRISPY CHICKEN offer delivery process completed');
|
|
}, 3000);
|
|
}, 1000);
|
|
}
|
|
|
|
// Template selection function
|
|
function selectTemplate(templateName) {
|
|
// Update active button
|
|
document.querySelectorAll('.template-btn').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
event.target.classList.add('active');
|
|
|
|
// Update textarea with selected template
|
|
document.getElementById('offerMessage').value = offerTemplates[templateName];
|
|
|
|
// Update preview
|
|
updateOfferPreview();
|
|
|
|
// Show notification
|
|
showToast(`Template "${templateName}" selected`, 'info');
|
|
}
|
|
|
|
// Customer Management Functions
|
|
function toggleCustomerForm() {
|
|
const form = document.getElementById('customerForm');
|
|
if (form.style.display === 'none') {
|
|
form.style.display = 'block';
|
|
document.getElementById('addCustomerForm').reset();
|
|
} else {
|
|
form.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function addCustomer(e) {
|
|
e.preventDefault();
|
|
|
|
const name = document.getElementById('customerName').value;
|
|
const phone = document.getElementById('customerPhone').value;
|
|
const email = document.getElementById('customerEmail').value;
|
|
const city = document.getElementById('customerCity').value;
|
|
const status = document.getElementById('customerStatus').value;
|
|
|
|
// Check if phone number already exists
|
|
const existingCustomer = customers.find(c => c.phone === phone);
|
|
if (existingCustomer) {
|
|
showToast('A customer with this phone number already exists', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Create new customer
|
|
const newCustomer = {
|
|
id: customers.length > 0 ? Math.max(...customers.map(c => c.id)) + 1 : 1,
|
|
name,
|
|
phone,
|
|
email,
|
|
city,
|
|
status,
|
|
createdAt: new Date()
|
|
};
|
|
|
|
// Add to customers array
|
|
customers.push(newCustomer);
|
|
saveCustomersToStorage();
|
|
|
|
// Update UI
|
|
updateCustomerList();
|
|
updateCustomerCount();
|
|
updateStatistics();
|
|
|
|
// Hide form
|
|
toggleCustomerForm();
|
|
|
|
// Show success message
|
|
showToast('Customer added successfully', 'success');
|
|
addNotification('success', `Customer ${name} added successfully`);
|
|
}
|
|
|
|
function updateCustomerList() {
|
|
const customerList = document.getElementById('customerList');
|
|
customerList.innerHTML = '';
|
|
|
|
if (customers.length === 0) {
|
|
customerList.innerHTML = `
|
|
<div class="empty-state">
|
|
<i class="bi bi-people"></i>
|
|
<p>No customers found</p>
|
|
<button class="btn btn-sm btn-primary" onclick="toggleCustomerForm()">
|
|
<i class="bi bi-plus"></i> Add Customer
|
|
</button>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
customers.forEach(customer => {
|
|
const customerItem = document.createElement('div');
|
|
customerItem.className = 'customer-item';
|
|
if (selectedCustomers.has(customer.id)) {
|
|
customerItem.classList.add('selected');
|
|
}
|
|
|
|
customerItem.innerHTML = `
|
|
<div class="customer-info">
|
|
<div class="customer-name">${customer.name}</div>
|
|
<div class="customer-contact">
|
|
<i class="bi bi-telephone"></i> ${customer.phone}
|
|
${customer.email ? `<br><i class="bi bi-envelope"></i> ${customer.email}` : ''}
|
|
${customer.city ? `<br><i class="bi bi-geo-alt"></i> ${customer.city}` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="customer-actions">
|
|
<input type="checkbox" class="customer-checkbox" data-id="${customer.id}" onchange="toggleCustomerSelection(${customer.id})">
|
|
<span class="customer-status ${customer.status}">${customer.status}</span>
|
|
<button class="btn btn-sm btn-outline-primary btn-icon" onclick="editCustomer(${customer.id})" title="Edit">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger btn-icon" onclick="deleteCustomer(${customer.id})" title="Delete">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
customerList.appendChild(customerItem);
|
|
});
|
|
|
|
// Update bulk actions visibility
|
|
updateBulkCustomerActions();
|
|
}
|
|
|
|
function filterCustomers() {
|
|
const searchTerm = document.getElementById('customerSearchInput').value.toLowerCase();
|
|
const activeFilter = document.querySelector('.customer-filter-btn.active').dataset.filter;
|
|
|
|
let filteredCustomers = [...customers];
|
|
|
|
// Filter by search term
|
|
if (searchTerm) {
|
|
filteredCustomers = filteredCustomers.filter(customer =>
|
|
customer.name.toLowerCase().includes(searchTerm) ||
|
|
customer.phone.toLowerCase().includes(searchTerm) ||
|
|
(customer.email && customer.email.toLowerCase().includes(searchTerm)) ||
|
|
(customer.city && customer.city.toLowerCase().includes(searchTerm))
|
|
);
|
|
}
|
|
|
|
// Filter by status or city
|
|
if (activeFilter !== 'all') {
|
|
if (activeFilter === 'active' || activeFilter === 'inactive') {
|
|
filteredCustomers = filteredCustomers.filter(customer => customer.status === activeFilter);
|
|
} else {
|
|
// City filter
|
|
filteredCustomers = filteredCustomers.filter(customer =>
|
|
customer.city && customer.city.toLowerCase() === activeFilter
|
|
);
|
|
}
|
|
}
|
|
|
|
// Update the customer list
|
|
const customerList = document.getElementById('customerList');
|
|
customerList.innerHTML = '';
|
|
|
|
if (filteredCustomers.length === 0) {
|
|
customerList.innerHTML = `
|
|
<div class="empty-state">
|
|
<i class="bi bi-people"></i>
|
|
<p>No customers found</p>
|
|
<button class="btn btn-sm btn-primary" onclick="toggleCustomerForm()">
|
|
<i class="bi bi-plus"></i> Add Customer
|
|
</button>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
filteredCustomers.forEach(customer => {
|
|
const customerItem = document.createElement('div');
|
|
customerItem.className = 'customer-item';
|
|
if (selectedCustomers.has(customer.id)) {
|
|
customerItem.classList.add('selected');
|
|
}
|
|
|
|
customerItem.innerHTML = `
|
|
<div class="customer-info">
|
|
<div class="customer-name">${customer.name}</div>
|
|
<div class="customer-contact">
|
|
<i class="bi bi-telephone"></i> ${customer.phone}
|
|
${customer.email ? `<br><i class="bi bi-envelope"></i> ${customer.email}` : ''}
|
|
${customer.city ? `<br><i class="bi bi-geo-alt"></i> ${customer.city}` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="customer-actions">
|
|
<input type="checkbox" class="customer-checkbox" data-id="${customer.id}" onchange="toggleCustomerSelection(${customer.id})">
|
|
<span class="customer-status ${customer.status}">${customer.status}</span>
|
|
<button class="btn btn-sm btn-outline-primary btn-icon" onclick="editCustomer(${customer.id})" title="Edit">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger btn-icon" onclick="deleteCustomer(${customer.id})" title="Delete">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
customerList.appendChild(customerItem);
|
|
});
|
|
|
|
// Update bulk actions visibility
|
|
updateBulkCustomerActions();
|
|
}
|
|
|
|
function toggleCustomerSelection(customerId) {
|
|
if (selectedCustomers.has(customerId)) {
|
|
selectedCustomers.delete(customerId);
|
|
} else {
|
|
selectedCustomers.add(customerId);
|
|
}
|
|
|
|
// Update UI
|
|
updateCustomerList();
|
|
}
|
|
|
|
function updateBulkCustomerActions() {
|
|
const bulkActions = document.getElementById('bulkCustomerActions');
|
|
if (selectedCustomers.size > 0) {
|
|
bulkActions.classList.add('active');
|
|
} else {
|
|
bulkActions.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
function bulkChangeStatus(status) {
|
|
if (selectedCustomers.size === 0) return;
|
|
|
|
selectedCustomers.forEach(customerId => {
|
|
const customer = customers.find(c => c.id === customerId);
|
|
if (customer) {
|
|
customer.status = status;
|
|
}
|
|
});
|
|
|
|
saveCustomersToStorage();
|
|
updateCustomerList();
|
|
updateCustomerCount();
|
|
updateStatistics();
|
|
|
|
showToast(`${selectedCustomers.size} customers set to ${status}`, 'success');
|
|
addNotification('success', `${selectedCustomers.size} customers set to ${status}`);
|
|
|
|
// Clear selection
|
|
selectedCustomers.clear();
|
|
updateBulkCustomerActions();
|
|
}
|
|
|
|
function bulkDeleteCustomers() {
|
|
if (selectedCustomers.size === 0) return;
|
|
|
|
if (!confirm(`Are you sure you want to delete ${selectedCustomers.size} customers?`)) return;
|
|
|
|
customers = customers.filter(customer => !selectedCustomers.has(customer.id));
|
|
saveCustomersToStorage();
|
|
|
|
updateCustomerList();
|
|
updateCustomerCount();
|
|
updateStatistics();
|
|
|
|
showToast(`${selectedCustomers.size} customers deleted`, 'success');
|
|
addNotification('success', `${selectedCustomers.size} customers deleted`);
|
|
|
|
// Clear selection
|
|
selectedCustomers.clear();
|
|
updateBulkCustomerActions();
|
|
}
|
|
|
|
function bulkSendOffer() {
|
|
if (selectedCustomers.size === 0) return;
|
|
|
|
const whatsappNumber = document.getElementById('whatsappNumber').value;
|
|
const offerMessage = document.getElementById('offerMessage').value;
|
|
|
|
// Ensure the message contains "CRISPY CHICKEN"
|
|
if (!offerMessage.includes('CRISPY CHICKEN')) {
|
|
showToast('The offer message must include "CRISPY CHICKEN"', 'warning');
|
|
return;
|
|
}
|
|
|
|
const selectedCustomersArray = customers.filter(customer => selectedCustomers.has(customer.id));
|
|
|
|
// Create new WhatsApp offers for each selected customer
|
|
const newOffers = selectedCustomersArray.map((customer, index) => {
|
|
return {
|
|
id: 'CRS' + String(crispyOffers.length + index + 1).padStart(3, '0'),
|
|
recipient: customer.name,
|
|
phone: customer.phone,
|
|
message: offerMessage,
|
|
platform: 'WhatsApp',
|
|
status: 'pending',
|
|
sentAt: new Date(),
|
|
deliveredAt: null,
|
|
retryCount: 0,
|
|
whatsappNumber: whatsappNumber
|
|
};
|
|
});
|
|
|
|
// Add the new offers to the crispyOffers array
|
|
crispyOffers = [...newOffers, ...crispyOffers];
|
|
|
|
// Also add to the messages array for tracking
|
|
messages = [...newOffers, ...messages];
|
|
saveMessagesToStorage();
|
|
|
|
// Update the UI
|
|
updateCustomerCount();
|
|
updateWhatsappStats();
|
|
updateCrispyStats();
|
|
applyFilters();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
|
|
showToast(`Sending CRISPY CHICKEN offer to ${selectedCustomersArray.length} selected customers...`, 'crispy');
|
|
addNotification('crispy', `Sending CRISPY CHICKEN offer to ${selectedCustomersArray.length} selected customers`);
|
|
|
|
// Simulate sending process
|
|
setTimeout(() => {
|
|
// Update status to 'crispy'
|
|
newOffers.forEach(offer => {
|
|
const index = messages.findIndex(m => m.id === offer.id);
|
|
if (index !== -1) {
|
|
messages[index].status = 'crispy';
|
|
}
|
|
|
|
const offerIndex = crispyOffers.findIndex(o => o.id === offer.id);
|
|
if (offerIndex !== -1) {
|
|
crispyOffers[offerIndex].status = 'sent';
|
|
}
|
|
});
|
|
|
|
saveMessagesToStorage();
|
|
updateWhatsappStats();
|
|
updateCrispyStats();
|
|
applyFilters();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
showToast('CRISPY CHICKEN offers sent successfully', 'crispy');
|
|
addNotification('success', 'CRISPY CHICKEN offers sent successfully');
|
|
|
|
// Simulate delivery after a delay
|
|
setTimeout(() => {
|
|
newOffers.forEach(offer => {
|
|
const index = messages.findIndex(m => m.id === offer.id);
|
|
if (index !== -1) {
|
|
// 90% success rate for delivery
|
|
const success = Math.random() > 0.1;
|
|
if (success) {
|
|
messages[index].status = 'delivered';
|
|
messages[index].deliveredAt = new Date();
|
|
|
|
const offerIndex = crispyOffers.findIndex(o => o.id === offer.id);
|
|
if (offerIndex !== -1) {
|
|
crispyOffers[offerIndex].status = 'delivered';
|
|
crispyOffers[offerIndex].deliveredAt = new Date();
|
|
}
|
|
} else {
|
|
messages[index].status = 'failed';
|
|
|
|
const offerIndex = crispyOffers.findIndex(o => o.id === offer.id);
|
|
if (offerIndex !== -1) {
|
|
crispyOffers[offerIndex].status = 'failed';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
saveMessagesToStorage();
|
|
updateWhatsappStats();
|
|
updateCrispyStats();
|
|
applyFilters();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
showToast('CRISPY CHICKEN offer delivery process completed', 'crispy');
|
|
addNotification('info', 'CRISPY CHICKEN offer delivery process completed');
|
|
}, 3000);
|
|
}, 1000);
|
|
|
|
// Clear selection
|
|
selectedCustomers.clear();
|
|
updateBulkCustomerActions();
|
|
}
|
|
|
|
function editCustomer(customerId) {
|
|
const customer = customers.find(c => c.id === customerId);
|
|
if (!customer) return;
|
|
|
|
// Populate the edit form
|
|
document.getElementById('editCustomerId').value = customer.id;
|
|
document.getElementById('editCustomerName').value = customer.name;
|
|
document.getElementById('editCustomerPhone').value = customer.phone;
|
|
document.getElementById('editCustomerEmail').value = customer.email || '';
|
|
document.getElementById('editCustomerCity').value = customer.city || '';
|
|
document.getElementById('editCustomerStatus').value = customer.status;
|
|
|
|
// Show the modal
|
|
const modal = new bootstrap.Modal(document.getElementById('customerModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function saveEditedCustomer() {
|
|
const customerId = parseInt(document.getElementById('editCustomerId').value);
|
|
const name = document.getElementById('editCustomerName').value;
|
|
const phone = document.getElementById('editCustomerPhone').value;
|
|
const email = document.getElementById('editCustomerEmail').value;
|
|
const city = document.getElementById('editCustomerCity').value;
|
|
const status = document.getElementById('editCustomerStatus').value;
|
|
|
|
// Find the customer index
|
|
const customerIndex = customers.findIndex(c => c.id === customerId);
|
|
if (customerIndex === -1) return;
|
|
|
|
// Check if phone number already exists for another customer
|
|
const existingCustomer = customers.find(c => c.phone === phone && c.id !== customerId);
|
|
if (existingCustomer) {
|
|
showToast('A customer with this phone number already exists', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Update customer data
|
|
customers[customerIndex] = {
|
|
...customers[customerIndex],
|
|
name,
|
|
phone,
|
|
email,
|
|
city,
|
|
status
|
|
};
|
|
|
|
saveCustomersToStorage();
|
|
|
|
// Update UI
|
|
updateCustomerList();
|
|
updateCustomerCount();
|
|
updateStatistics();
|
|
|
|
// Hide the modal
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('customerModal'));
|
|
modal.hide();
|
|
|
|
// Show success message
|
|
showToast('Customer updated successfully', 'success');
|
|
addNotification('success', `Customer ${name} updated successfully`);
|
|
}
|
|
|
|
function deleteCustomer(customerId) {
|
|
if (!confirm('Are you sure you want to delete this customer?')) return;
|
|
|
|
const customer = customers.find(c => c.id === customerId);
|
|
if (!customer) return;
|
|
|
|
// Remove customer from array
|
|
customers = customers.filter(c => c.id !== customerId);
|
|
saveCustomersToStorage();
|
|
|
|
// Update UI
|
|
updateCustomerList();
|
|
updateCustomerCount();
|
|
updateStatistics();
|
|
|
|
// Show success message
|
|
showToast('Customer deleted successfully', 'success');
|
|
addNotification('success', `Customer ${customer.name} deleted successfully`);
|
|
}
|
|
|
|
// LocalStorage Functions
|
|
function saveCustomersToStorage() {
|
|
localStorage.setItem('crispyCustomers', JSON.stringify(customers));
|
|
}
|
|
|
|
function loadCustomersFromStorage() {
|
|
const storedCustomers = localStorage.getItem('crispyCustomers');
|
|
if (storedCustomers) {
|
|
customers = JSON.parse(storedCustomers);
|
|
|
|
// Convert date strings back to Date objects
|
|
customers.forEach(customer => {
|
|
if (customer.createdAt) {
|
|
customer.createdAt = new Date(customer.createdAt);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function saveMessagesToStorage() {
|
|
localStorage.setItem('crispyMessages', JSON.stringify(messages));
|
|
}
|
|
|
|
function loadMessagesFromStorage() {
|
|
const storedMessages = localStorage.getItem('crispyMessages');
|
|
if (storedMessages) {
|
|
messages = JSON.parse(storedMessages);
|
|
|
|
// Convert date strings back to Date objects
|
|
messages.forEach(message => {
|
|
if (message.sentAt) {
|
|
message.sentAt = new Date(message.sentAt);
|
|
}
|
|
if (message.deliveredAt) {
|
|
message.deliveredAt = new Date(message.deliveredAt);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function saveScheduledOffersToStorage() {
|
|
localStorage.setItem('crispyScheduledOffers', JSON.stringify(scheduledOffers));
|
|
}
|
|
|
|
function loadScheduledOffersFromStorage() {
|
|
const storedScheduledOffers = localStorage.getItem('crispyScheduledOffers');
|
|
if (storedScheduledOffers) {
|
|
scheduledOffers = JSON.parse(storedScheduledOffers);
|
|
|
|
// Convert date strings back to Date objects
|
|
scheduledOffers.forEach(offer => {
|
|
if (offer.scheduledAt) {
|
|
offer.scheduledAt = new Date(offer.scheduledAt);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// File Upload Functions
|
|
function showUploadModal() {
|
|
const modal = new bootstrap.Modal(document.getElementById('uploadModal'));
|
|
modal.show();
|
|
|
|
// Reset form
|
|
resetUploadForm();
|
|
}
|
|
|
|
function resetUploadForm() {
|
|
selectedFile = null;
|
|
document.getElementById('fileInput').value = '';
|
|
document.getElementById('fileInfo').classList.remove('active');
|
|
document.getElementById('uploadProgress').classList.remove('active');
|
|
document.getElementById('uploadResults').classList.remove('active');
|
|
document.getElementById('uploadBtn').disabled = true;
|
|
}
|
|
|
|
function handleDragOver(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
document.getElementById('fileUploadArea').classList.add('dragover');
|
|
}
|
|
|
|
function handleDragLeave(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
document.getElementById('fileUploadArea').classList.remove('dragover');
|
|
}
|
|
|
|
function handleDrop(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
document.getElementById('fileUploadArea').classList.remove('dragover');
|
|
|
|
const files = e.dataTransfer.files;
|
|
if (files.length > 0) {
|
|
handleFile(files[0]);
|
|
}
|
|
}
|
|
|
|
function handleFileSelect(e) {
|
|
const files = e.target.files;
|
|
if (files.length > 0) {
|
|
handleFile(files[0]);
|
|
}
|
|
}
|
|
|
|
function handleFile(file) {
|
|
// Check file type
|
|
const validTypes = ['text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
|
|
if (!validTypes.includes(file.type)) {
|
|
showToast('Please select a valid CSV or Excel file', 'error');
|
|
return;
|
|
}
|
|
|
|
selectedFile = file;
|
|
|
|
// Display file info
|
|
document.getElementById('fileName').textContent = file.name;
|
|
document.getElementById('fileSize').textContent = formatFileSize(file.size);
|
|
document.getElementById('fileInfo').classList.add('active');
|
|
document.getElementById('uploadBtn').disabled = false;
|
|
}
|
|
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
function processFile() {
|
|
if (!selectedFile) return;
|
|
|
|
// Show progress
|
|
document.getElementById('uploadProgress').classList.add('active');
|
|
document.getElementById('progressBar').style.width = '0%';
|
|
document.getElementById('progressText').textContent = 'Processing...';
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = function(e) {
|
|
try {
|
|
let data = [];
|
|
|
|
if (selectedFile.name.endsWith('.csv')) {
|
|
// Parse CSV
|
|
data = parseCSV(e.target.result);
|
|
} else {
|
|
// Parse Excel
|
|
data = parseExcel(e.target.result);
|
|
}
|
|
|
|
// Process data
|
|
processCustomerData(data);
|
|
} catch (error) {
|
|
showToast('Error processing file: ' + error.message, 'error');
|
|
resetUploadProgress();
|
|
}
|
|
};
|
|
|
|
reader.onerror = function() {
|
|
showToast('Error reading file', 'error');
|
|
resetUploadProgress();
|
|
};
|
|
|
|
// Read file
|
|
if (selectedFile.name.endsWith('.csv')) {
|
|
reader.readAsText(selectedFile);
|
|
} else {
|
|
reader.readAsArrayBuffer(selectedFile);
|
|
}
|
|
}
|
|
|
|
function parseCSV(csvText) {
|
|
const lines = csvText.split('\n');
|
|
const headers = lines[0].split(',').map(h => h.trim());
|
|
const data = [];
|
|
|
|
for (let i = 1; i < lines.length; i++) {
|
|
if (lines[i].trim() === '') continue;
|
|
|
|
const values = lines[i].split(',').map(v => v.trim());
|
|
const row = {};
|
|
|
|
headers.forEach((header, index) => {
|
|
row[header] = values[index] || '';
|
|
});
|
|
|
|
data.push(row);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
function parseExcel(arrayBuffer) {
|
|
const workbook = XLSX.read(arrayBuffer, { type: 'array' });
|
|
const sheetName = workbook.SheetNames[0];
|
|
const worksheet = workbook.Sheets[sheetName];
|
|
const data = XLSX.utils.sheet_to_json(worksheet);
|
|
|
|
return data;
|
|
}
|
|
|
|
function processCustomerData(data) {
|
|
let successCount = 0;
|
|
let duplicateCount = 0;
|
|
let errorCount = 0;
|
|
|
|
// Update progress
|
|
updateProgress(10, 'Validating data...');
|
|
|
|
// Process each row
|
|
data.forEach((row, index) => {
|
|
try {
|
|
// Validate required fields
|
|
if (!row.name || !row.phone) {
|
|
errorCount++;
|
|
return;
|
|
}
|
|
|
|
// Check if phone already exists
|
|
const existingCustomer = customers.find(c => c.phone === row.phone);
|
|
if (existingCustomer) {
|
|
duplicateCount++;
|
|
return;
|
|
}
|
|
|
|
// Create new customer
|
|
const newCustomer = {
|
|
id: customers.length > 0 ? Math.max(...customers.map(c => c.id)) + 1 : 1,
|
|
name: row.name,
|
|
phone: row.phone,
|
|
email: row.email || '',
|
|
city: row.city || '',
|
|
status: row.status || 'active',
|
|
createdAt: new Date()
|
|
};
|
|
|
|
// Add to customers array
|
|
customers.push(newCustomer);
|
|
successCount++;
|
|
|
|
// Update progress
|
|
const progress = 10 + (index + 1) / data.length * 80;
|
|
updateProgress(progress, `Processing row ${index + 1} of ${data.length}...`);
|
|
|
|
} catch (error) {
|
|
errorCount++;
|
|
}
|
|
});
|
|
|
|
// Save to localStorage
|
|
saveCustomersToStorage();
|
|
|
|
// Update progress
|
|
updateProgress(100, 'Completed!');
|
|
|
|
// Show results
|
|
setTimeout(() => {
|
|
document.getElementById('totalRecords').textContent = data.length;
|
|
document.getElementById('successCount').textContent = successCount;
|
|
document.getElementById('duplicateCount').textContent = duplicateCount;
|
|
document.getElementById('errorCount').textContent = errorCount;
|
|
document.getElementById('uploadResults').classList.add('active');
|
|
|
|
// Update UI
|
|
updateCustomerList();
|
|
updateCustomerCount();
|
|
updateStatistics();
|
|
|
|
// Show success message
|
|
showToast(`Upload completed: ${successCount} customers added, ${duplicateCount} duplicates skipped, ${errorCount} errors`, 'success');
|
|
addNotification('success', `Upload completed: ${successCount} customers added`);
|
|
|
|
// Reset progress after delay
|
|
setTimeout(() => {
|
|
resetUploadProgress();
|
|
}, 3000);
|
|
}, 500);
|
|
}
|
|
|
|
function updateProgress(percent, text) {
|
|
document.getElementById('progressBar').style.width = percent + '%';
|
|
document.getElementById('progressText').textContent = text;
|
|
}
|
|
|
|
function resetUploadProgress() {
|
|
document.getElementById('uploadProgress').classList.remove('active');
|
|
document.getElementById('progressBar').style.width = '0%';
|
|
document.getElementById('progressText').textContent = 'Processing...';
|
|
}
|
|
|
|
// Message Selection Functions
|
|
function toggleSelectAllMessages() {
|
|
const selectAll = document.getElementById('selectAllMessages').checked;
|
|
const checkboxes = document.querySelectorAll('.message-checkbox');
|
|
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.checked = selectAll;
|
|
const messageId = checkbox.dataset.id;
|
|
if (selectAll) {
|
|
selectedMessages.add(messageId);
|
|
} else {
|
|
selectedMessages.delete(messageId);
|
|
}
|
|
});
|
|
|
|
updateBulkMessageActions();
|
|
}
|
|
|
|
function toggleMessageSelection(messageId) {
|
|
if (selectedMessages.has(messageId)) {
|
|
selectedMessages.delete(messageId);
|
|
} else {
|
|
selectedMessages.add(messageId);
|
|
}
|
|
|
|
updateBulkMessageActions();
|
|
updateSelectAllCheckbox();
|
|
}
|
|
|
|
function updateSelectAllCheckbox() {
|
|
const selectAllCheckbox = document.getElementById('selectAllMessages');
|
|
const checkboxes = document.querySelectorAll('.message-checkbox');
|
|
|
|
if (checkboxes.length === 0) {
|
|
selectAllCheckbox.checked = false;
|
|
selectAllCheckbox.indeterminate = false;
|
|
return;
|
|
}
|
|
|
|
const checkedCount = document.querySelectorAll('.message-checkbox:checked').length;
|
|
|
|
if (checkedCount === 0) {
|
|
selectAllCheckbox.checked = false;
|
|
selectAllCheckbox.indeterminate = false;
|
|
} else if (checkedCount === checkboxes.length) {
|
|
selectAllCheckbox.checked = true;
|
|
selectAllCheckbox.indeterminate = false;
|
|
} else {
|
|
selectAllCheckbox.checked = false;
|
|
selectAllCheckbox.indeterminate = true;
|
|
}
|
|
}
|
|
|
|
function updateBulkMessageActions() {
|
|
const bulkActions = document.getElementById('bulkMessageActions');
|
|
const selectedCount = document.getElementById('selectedMessagesCount');
|
|
|
|
if (selectedMessages.size > 0) {
|
|
bulkActions.style.display = 'block';
|
|
selectedCount.textContent = selectedMessages.size;
|
|
} else {
|
|
bulkActions.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function bulkResendMessages() {
|
|
if (selectedMessages.size === 0) return;
|
|
|
|
selectedMessages.forEach(messageId => {
|
|
const messageIndex = messages.findIndex(m => m.id === messageId);
|
|
if (messageIndex !== -1) {
|
|
// Update message status and retry count
|
|
messages[messageIndex].status = 'pending';
|
|
messages[messageIndex].retryCount = (messages[messageIndex].retryCount || 0) + 1;
|
|
messages[messageIndex].sentAt = new Date();
|
|
}
|
|
});
|
|
|
|
saveMessagesToStorage();
|
|
|
|
// Show success toast
|
|
showToast(`${selectedMessages.size} messages resent`, 'success');
|
|
addNotification('success', `${selectedMessages.size} messages resent`);
|
|
|
|
// Apply filters to update the view
|
|
applyFilters();
|
|
|
|
// Simulate delivery after a delay
|
|
setTimeout(() => {
|
|
selectedMessages.forEach(messageId => {
|
|
const messageIndex = messages.findIndex(m => m.id === messageId);
|
|
if (messageIndex !== -1) {
|
|
const success = Math.random() > 0.3; // 70% success rate for retries
|
|
|
|
if (success) {
|
|
messages[messageIndex].status = 'delivered';
|
|
messages[messageIndex].deliveredAt = new Date();
|
|
} else {
|
|
messages[messageIndex].status = 'failed';
|
|
}
|
|
}
|
|
});
|
|
|
|
saveMessagesToStorage();
|
|
applyFilters();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
}, 3000);
|
|
|
|
// Clear selection
|
|
selectedMessages.clear();
|
|
updateBulkMessageActions();
|
|
}
|
|
|
|
function bulkDeleteMessages() {
|
|
if (selectedMessages.size === 0) return;
|
|
|
|
if (!confirm(`Are you sure you want to delete ${selectedMessages.size} messages?`)) return;
|
|
|
|
messages = messages.filter(message => !selectedMessages.has(message.id));
|
|
saveMessagesToStorage();
|
|
|
|
showToast(`${selectedMessages.size} messages deleted`, 'success');
|
|
addNotification('success', `${selectedMessages.size} messages deleted`);
|
|
|
|
// Apply filters to update the view
|
|
applyFilters();
|
|
updateStatistics();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
|
|
// Clear selection
|
|
selectedMessages.clear();
|
|
updateBulkMessageActions();
|
|
}
|
|
|
|
function clearMessageSelection() {
|
|
selectedMessages.clear();
|
|
document.querySelectorAll('.message-checkbox').forEach(checkbox => {
|
|
checkbox.checked = false;
|
|
});
|
|
updateBulkMessageActions();
|
|
updateSelectAllCheckbox();
|
|
}
|
|
|
|
// Recent Activity Functions
|
|
function updateRecentActivity() {
|
|
const recentActivity = document.getElementById('recentActivity');
|
|
recentActivity.innerHTML = '';
|
|
|
|
// Get the 10 most recent messages
|
|
const recentMessages = [...messages]
|
|
.sort((a, b) => new Date(b.sentAt) - new Date(a.sentAt))
|
|
.slice(0, 10);
|
|
|
|
if (recentMessages.length === 0) {
|
|
recentActivity.innerHTML = '<p class="text-center text-muted">No recent activity</p>';
|
|
return;
|
|
}
|
|
|
|
recentMessages.forEach(message => {
|
|
const activityItem = document.createElement('div');
|
|
activityItem.className = 'activity-item';
|
|
|
|
// Format time
|
|
const sentTime = new Date(message.sentAt);
|
|
const now = new Date();
|
|
const diffMs = now - sentTime;
|
|
const diffMins = Math.floor(diffMs / 60000);
|
|
const diffHours = Math.floor(diffMs / 3600000);
|
|
const diffDays = Math.floor(diffMs / 86400000);
|
|
|
|
let timeText = '';
|
|
if (diffMins < 1) {
|
|
timeText = 'Just now';
|
|
} else if (diffMins < 60) {
|
|
timeText = `${diffMins} min ago`;
|
|
} else if (diffHours < 24) {
|
|
timeText = `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
|
} else {
|
|
timeText = `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
|
}
|
|
|
|
let iconClass = '';
|
|
switch (message.status) {
|
|
case 'sent':
|
|
iconClass = 'bi-send';
|
|
break;
|
|
case 'delivered':
|
|
iconClass = 'bi-check-circle';
|
|
break;
|
|
case 'failed':
|
|
iconClass = 'bi-exclamation-circle';
|
|
break;
|
|
case 'crispy':
|
|
iconClass = 'bi-fire';
|
|
break;
|
|
default:
|
|
iconClass = 'bi-clock';
|
|
}
|
|
|
|
activityItem.innerHTML = `
|
|
<div class="activity-icon ${message.status}">
|
|
<i class="bi ${iconClass}"></i>
|
|
</div>
|
|
<div class="activity-content">
|
|
<div class="activity-title">${message.status === 'crispy' ? 'CRISPY CHICKEN offer' : 'Message'} ${message.status === 'delivered' ? 'delivered to' : 'sent to'} ${message.recipient}</div>
|
|
<div class="activity-time">${timeText}</div>
|
|
</div>
|
|
`;
|
|
|
|
recentActivity.appendChild(activityItem);
|
|
});
|
|
}
|
|
|
|
// Notification Functions
|
|
function addNotification(type, message) {
|
|
const notification = {
|
|
id: Date.now(),
|
|
type,
|
|
message,
|
|
timestamp: new Date()
|
|
};
|
|
|
|
notifications.unshift(notification);
|
|
|
|
// Keep only the latest 20 notifications
|
|
if (notifications.length > 20) {
|
|
notifications = notifications.slice(0, 20);
|
|
}
|
|
|
|
// Save to localStorage
|
|
localStorage.setItem('crispyNotifications', JSON.stringify(notifications));
|
|
|
|
// Update notification badge
|
|
updateNotificationBadge();
|
|
|
|
// Show desktop notification if enabled
|
|
const settings = JSON.parse(localStorage.getItem('crispySettings')) || {};
|
|
if (settings.desktopNotifications && 'Notification' in window && Notification.permission === 'granted') {
|
|
new Notification('Message Tracking System', {
|
|
body: message,
|
|
icon: '/favicon.ico'
|
|
});
|
|
}
|
|
}
|
|
|
|
function updateNotificationBadge() {
|
|
const badge = document.getElementById('notificationBadge');
|
|
const unreadCount = notifications.filter(n => !n.read).length;
|
|
|
|
if (unreadCount > 0) {
|
|
badge.textContent = unreadCount;
|
|
badge.style.display = 'flex';
|
|
} else {
|
|
badge.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function showNotifications() {
|
|
const modal = new bootstrap.Modal(document.getElementById('notificationsModal'));
|
|
modal.show();
|
|
|
|
const notificationsList = document.getElementById('notificationsList');
|
|
notificationsList.innerHTML = '';
|
|
|
|
if (notifications.length === 0) {
|
|
notificationsList.innerHTML = '<p class="text-center text-muted">No notifications</p>';
|
|
return;
|
|
}
|
|
|
|
notifications.forEach(notification => {
|
|
const notificationItem = document.createElement('div');
|
|
notificationItem.className = 'card mb-2';
|
|
|
|
let iconClass = '';
|
|
switch (notification.type) {
|
|
case 'success':
|
|
iconClass = 'bi-check-circle-fill text-success';
|
|
break;
|
|
case 'error':
|
|
iconClass = 'bi-exclamation-circle-fill text-danger';
|
|
break;
|
|
case 'warning':
|
|
iconClass = 'bi-exclamation-triangle-fill text-warning';
|
|
break;
|
|
case 'crispy':
|
|
iconClass = 'bi-fire-fill text-danger';
|
|
break;
|
|
case 'info':
|
|
default:
|
|
iconClass = 'bi-info-circle-fill text-info';
|
|
}
|
|
|
|
// Format time
|
|
const time = new Date(notification.timestamp);
|
|
const timeText = time.toLocaleString();
|
|
|
|
notificationItem.innerHTML = `
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-start">
|
|
<i class="bi ${iconClass} me-2"></i>
|
|
<div>
|
|
<div>${notification.message}</div>
|
|
<small class="text-muted">${timeText}</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
notificationsList.appendChild(notificationItem);
|
|
});
|
|
|
|
// Mark all as read
|
|
notifications.forEach(n => n.read = true);
|
|
localStorage.setItem('crispyNotifications', JSON.stringify(notifications));
|
|
updateNotificationBadge();
|
|
}
|
|
|
|
function clearAllNotifications() {
|
|
notifications = [];
|
|
localStorage.setItem('crispyNotifications', JSON.stringify(notifications));
|
|
updateNotificationBadge();
|
|
|
|
const notificationsList = document.getElementById('notificationsList');
|
|
notificationsList.innerHTML = '<p class="text-center text-muted">No notifications</p>';
|
|
}
|
|
|
|
// Message Simulation Functions
|
|
function startSimulation() {
|
|
if (simulationInterval) return;
|
|
|
|
addSimulationLog('info', 'Simulation started');
|
|
|
|
simulationInterval = setInterval(() => {
|
|
// Randomly update message statuses
|
|
const pendingMessages = messages.filter(m => m.status === 'pending');
|
|
if (pendingMessages.length > 0) {
|
|
const randomMessage = pendingMessages[Math.floor(Math.random() * pendingMessages.length)];
|
|
const success = Math.random() > 0.2; // 80% success rate
|
|
|
|
if (success) {
|
|
randomMessage.status = 'delivered';
|
|
randomMessage.deliveredAt = new Date();
|
|
addSimulationLog('success', `Message ${randomMessage.id} delivered to ${randomMessage.recipient}`);
|
|
addNotification('success', `Message ${randomMessage.id} delivered to ${randomMessage.recipient}`);
|
|
} else {
|
|
randomMessage.status = 'failed';
|
|
addSimulationLog('error', `Message ${randomMessage.id} failed to deliver to ${randomMessage.recipient}`);
|
|
addNotification('error', `Message ${randomMessage.id} failed to deliver to ${randomMessage.recipient}`);
|
|
}
|
|
|
|
saveMessagesToStorage();
|
|
applyFilters();
|
|
updateStatistics();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
}
|
|
|
|
// Randomly add new messages
|
|
if (Math.random() > 0.7) {
|
|
addRandomMessage();
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
function pauseSimulation() {
|
|
if (simulationInterval) {
|
|
clearInterval(simulationInterval);
|
|
simulationInterval = null;
|
|
addSimulationLog('warning', 'Simulation paused');
|
|
}
|
|
}
|
|
|
|
function stopSimulation() {
|
|
if (simulationInterval) {
|
|
clearInterval(simulationInterval);
|
|
simulationInterval = null;
|
|
addSimulationLog('error', 'Simulation stopped');
|
|
}
|
|
}
|
|
|
|
function addRandomMessage() {
|
|
const recipients = ['John Doe', 'Jane Smith', 'Bob Johnson', 'Alice Brown', 'Charlie Davis'];
|
|
const platforms = ['SMS', 'WhatsApp', 'Email'];
|
|
const messagesList = [
|
|
'Your order has been shipped',
|
|
'Payment received successfully',
|
|
'Appointment reminder',
|
|
'Special offer just for you',
|
|
'Your account has been updated'
|
|
];
|
|
|
|
const newMessage = {
|
|
id: 'MSG' + String(messages.length + 1).padStart(3, '0'),
|
|
recipient: recipients[Math.floor(Math.random() * recipients.length)],
|
|
phone: '+97150' + Math.floor(Math.random() * 10000000),
|
|
message: messagesList[Math.floor(Math.random() * messagesList.length)],
|
|
platform: platforms[Math.floor(Math.random() * platforms.length)],
|
|
status: 'pending',
|
|
sentAt: new Date(),
|
|
deliveredAt: null,
|
|
retryCount: 0
|
|
};
|
|
|
|
messages.unshift(newMessage);
|
|
saveMessagesToStorage();
|
|
|
|
addSimulationLog('info', `New message ${newMessage.id} sent to ${newMessage.recipient}`);
|
|
addNotification('info', `New message ${newMessage.id} sent to ${newMessage.recipient}`);
|
|
|
|
applyFilters();
|
|
updateStatistics();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
}
|
|
|
|
function addSimulationLog(type, message) {
|
|
const simulationLog = document.getElementById('simulationLog');
|
|
const logEntry = document.createElement('div');
|
|
logEntry.className = 'log-entry';
|
|
|
|
const time = new Date().toLocaleTimeString();
|
|
|
|
logEntry.innerHTML = `
|
|
<span class="log-time">[${time}]</span>
|
|
<span class="log-status ${type}">${message}</span>
|
|
`;
|
|
|
|
simulationLog.appendChild(logEntry);
|
|
|
|
// Auto-scroll to bottom
|
|
simulationLog.scrollTop = simulationLog.scrollHeight;
|
|
|
|
// Keep only the latest 20 log entries
|
|
while (simulationLog.children.length > 20) {
|
|
simulationLog.removeChild(simulationLog.firstChild);
|
|
}
|
|
}
|
|
|
|
// Scheduled Offers Functions
|
|
function updateScheduledOffersList() {
|
|
const scheduledOffersList = document.getElementById('scheduledOffersList');
|
|
scheduledOffersList.innerHTML = '';
|
|
|
|
if (scheduledOffers.length === 0) {
|
|
scheduledOffersList.innerHTML = '<p class="text-center">No scheduled offers</p>';
|
|
return;
|
|
}
|
|
|
|
scheduledOffers.forEach(offer => {
|
|
const offerItem = document.createElement('div');
|
|
offerItem.className = 'scheduled-offer-item';
|
|
|
|
const scheduledTime = new Date(offer.scheduledAt).toLocaleString();
|
|
|
|
offerItem.innerHTML = `
|
|
<div class="scheduled-offer-info">
|
|
<div class="scheduled-offer-time">${scheduledTime}</div>
|
|
<div class="scheduled-offer-message">${offer.message.substring(0, 100)}${offer.message.length > 100 ? '...' : ''}</div>
|
|
</div>
|
|
<div class="scheduled-offer-actions">
|
|
<button class="btn btn-sm btn-danger" onclick="cancelScheduledOffer('${offer.id}')">
|
|
<i class="bi bi-x"></i> Cancel
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
scheduledOffersList.appendChild(offerItem);
|
|
});
|
|
}
|
|
|
|
function cancelScheduledOffer(offerId) {
|
|
scheduledOffers = scheduledOffers.filter(offer => offer.id !== offerId);
|
|
saveScheduledOffersToStorage();
|
|
updateScheduledOffersList();
|
|
showToast('Scheduled offer cancelled', 'success');
|
|
addNotification('success', 'Scheduled offer cancelled');
|
|
}
|
|
|
|
function checkScheduledOffers() {
|
|
// Check every minute if any scheduled offers need to be sent
|
|
setInterval(() => {
|
|
const now = new Date();
|
|
|
|
scheduledOffers.forEach((offer, index) => {
|
|
if (new Date(offer.scheduledAt) <= now) {
|
|
// Send the offer
|
|
sendScheduledOffer(offer);
|
|
|
|
// Remove from scheduled offers
|
|
scheduledOffers.splice(index, 1);
|
|
saveScheduledOffersToStorage();
|
|
updateScheduledOffersList();
|
|
}
|
|
});
|
|
}, 60000); // Check every minute
|
|
}
|
|
|
|
function sendScheduledOffer(offer) {
|
|
const activeCustomers = customers.filter(c => c.status === 'active');
|
|
|
|
if (activeCustomers.length === 0) {
|
|
showToast('No active customers found', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Create new WhatsApp offers for each active customer
|
|
const newOffers = activeCustomers.map((customer, index) => {
|
|
return {
|
|
id: 'CRS' + String(crispyOffers.length + index + 1).padStart(3, '0'),
|
|
recipient: customer.name,
|
|
phone: customer.phone,
|
|
message: offer.message,
|
|
platform: 'WhatsApp',
|
|
status: 'pending',
|
|
sentAt: new Date(),
|
|
deliveredAt: null,
|
|
retryCount: 0,
|
|
whatsappNumber: offer.whatsappNumber
|
|
};
|
|
});
|
|
|
|
// Add the new offers to the crispyOffers array
|
|
crispyOffers = [...newOffers, ...crispyOffers];
|
|
|
|
// Also add to the messages array for tracking
|
|
messages = [...newOffers, ...messages];
|
|
saveMessagesToStorage();
|
|
|
|
// Update the UI
|
|
updateCustomerCount();
|
|
updateWhatsappStats();
|
|
updateCrispyStats();
|
|
applyFilters();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
|
|
showToast(`Sending scheduled CRISPY CHICKEN offer to ${activeCustomers.length} customers...`, 'crispy');
|
|
addNotification('crispy', `Sending scheduled CRISPY CHICKEN offer to ${activeCustomers.length} customers`);
|
|
|
|
// Simulate sending process
|
|
setTimeout(() => {
|
|
// Update status to 'crispy'
|
|
newOffers.forEach(offer => {
|
|
const index = messages.findIndex(m => m.id === offer.id);
|
|
if (index !== -1) {
|
|
messages[index].status = 'crispy';
|
|
}
|
|
|
|
const offerIndex = crispyOffers.findIndex(o => o.id === offer.id);
|
|
if (offerIndex !== -1) {
|
|
crispyOffers[offerIndex].status = 'sent';
|
|
}
|
|
});
|
|
|
|
saveMessagesToStorage();
|
|
updateWhatsappStats();
|
|
updateCrispyStats();
|
|
applyFilters();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
showToast('CRISPY CHICKEN offers sent successfully', 'crispy');
|
|
addNotification('success', 'CRISPY CHICKEN offers sent successfully');
|
|
|
|
// Simulate delivery after a delay
|
|
setTimeout(() => {
|
|
newOffers.forEach(offer => {
|
|
const index = messages.findIndex(m => m.id === offer.id);
|
|
if (index !== -1) {
|
|
// 90% success rate for delivery
|
|
const success = Math.random() > 0.1;
|
|
if (success) {
|
|
messages[index].status = 'delivered';
|
|
messages[index].deliveredAt = new Date();
|
|
|
|
const offerIndex = crispyOffers.findIndex(o => o.id === offer.id);
|
|
if (offerIndex !== -1) {
|
|
crispyOffers[offerIndex].status = 'delivered';
|
|
crispyOffers[offerIndex].deliveredAt = new Date();
|
|
}
|
|
} else {
|
|
messages[index].status = 'failed';
|
|
|
|
const offerIndex = crispyOffers.findIndex(o => o.id === offer.id);
|
|
if (offerIndex !== -1) {
|
|
crispyOffers[offerIndex].status = 'failed';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
saveMessagesToStorage();
|
|
updateWhatsappStats();
|
|
updateCrispyStats();
|
|
applyFilters();
|
|
updateTodayStats();
|
|
updateRecentActivity();
|
|
showToast('CRISPY CHICKEN offer delivery process completed', 'crispy');
|
|
addNotification('info', 'CRISPY CHICKEN offer delivery process completed');
|
|
}, 3000);
|
|
}, 1000);
|
|
}
|
|
|
|
// Auto Refresh Functions
|
|
function setupAutoRefresh() {
|
|
// Clear existing interval
|
|
if (refreshIntervalId) {
|
|
clearInterval(refreshIntervalId);
|
|
}
|
|
|
|
const settings = JSON.parse(localStorage.getItem('crispySettings')) || {};
|
|
const refreshInterval = (settings.refreshInterval || 10) * 1000; // Convert to milliseconds
|
|
|
|
refreshIntervalId = setInterval(() => {
|
|
refreshData();
|
|
}, refreshInterval);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|