1438 lines
86 KiB
HTML
1438 lines
86 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||
<meta name="description" content="Admin Panel - BNKops Map - Interactive canvassing web-app & viewer">
|
||
<title>Admin Panel</title>
|
||
|
||
<!-- Favicon -->
|
||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||
<link rel="shortcut icon" href="/favicon.ico">
|
||
|
||
<!-- Leaflet CSS -->
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||
crossorigin="" />
|
||
|
||
<!-- Custom CSS -->
|
||
<link rel="stylesheet" href="css/style.css">
|
||
<link rel="stylesheet" href="css/admin.css">
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
<!-- Header -->
|
||
<header class="header">
|
||
<button id="mobile-menu-toggle" class="mobile-menu-toggle" aria-label="Toggle menu">
|
||
<span class="hamburger-icon">
|
||
<span></span>
|
||
<span></span>
|
||
<span></span>
|
||
</span>
|
||
</button>
|
||
<h1>Admin Panel</h1>
|
||
<div class="header-actions">
|
||
<a href="/" class="btn btn-secondary">← Back to Map</a>
|
||
<span id="admin-info" class="admin-info desktop-only"></span>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Main Content -->
|
||
<div class="admin-container">
|
||
<div class="admin-sidebar" id="admin-sidebar">
|
||
<div class="sidebar-header">
|
||
<h2>Admin Menu</h2>
|
||
<button id="close-sidebar" class="close-sidebar">×</button>
|
||
</div>
|
||
<nav class="admin-nav">
|
||
<a href="#dashboard">
|
||
<span class="nav-icon">📊</span>
|
||
<span class="nav-text">Dashboard</span>
|
||
</a>
|
||
<a href="#nocodb-links">
|
||
<span class="nav-icon">🗄️</span>
|
||
<span class="nav-text">NocoDB Links</span>
|
||
</a>
|
||
<a href="#listmonk-links">
|
||
<span class="nav-icon">📧</span>
|
||
<span class="nav-text">Listmonk Links</span>
|
||
</a>
|
||
<a href="#start-location" class="active">
|
||
<span class="nav-icon">📍</span>
|
||
<span class="nav-text">Start Location</span>
|
||
</a>
|
||
<a href="#walk-sheet">
|
||
<span class="nav-icon">📄</span>
|
||
<span class="nav-text">Walk Sheet</span>
|
||
</a>
|
||
<a href="#shifts">
|
||
<span class="nav-icon">📅</span>
|
||
<span class="nav-text">Shifts</span>
|
||
</a>
|
||
<a href="#users">
|
||
<span class="nav-icon">👥</span>
|
||
<span class="nav-text">Users</span>
|
||
</a>
|
||
<a href="#cuts">
|
||
<span class="nav-icon">✂️</span>
|
||
<span class="nav-text">Map Cuts</span>
|
||
</a>
|
||
<a href="#convert-data">
|
||
<span class="nav-icon">📊</span>
|
||
<span class="nav-text">Convert Data</span>
|
||
</a>
|
||
<a href="#listmonk">
|
||
<span class="nav-icon">📧</span>
|
||
<span class="nav-text">Email Lists</span>
|
||
</a>
|
||
</nav>
|
||
<div class="sidebar-footer">
|
||
<div id="mobile-admin-info" class="mobile-admin-info mobile-only"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="admin-content">
|
||
<!-- Dashboard Section -->
|
||
<section id="dashboard" class="admin-section" style="display: none;">
|
||
<h2>Campaign Dashboard</h2>
|
||
<p>Overview of campaign metrics and statistics</p>
|
||
|
||
<div class="dashboard-container">
|
||
<!-- Summary Cards -->
|
||
<div class="dashboard-cards">
|
||
<div class="dashboard-card">
|
||
<h3>Total Locations</h3>
|
||
<div class="card-value" id="total-locations">-</div>
|
||
</div>
|
||
<div class="dashboard-card">
|
||
<h3>Overall Score</h3>
|
||
<div class="card-value" id="overall-score">-</div>
|
||
<div class="card-subtitle">out of 4.0</div>
|
||
</div>
|
||
<div class="dashboard-card">
|
||
<h3>Signs Delivered</h3>
|
||
<div class="card-value" id="sign-delivered">-</div>
|
||
</div>
|
||
<div class="dashboard-card">
|
||
<h3>Total Users</h3>
|
||
<div class="card-value" id="total-users">-</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Charts -->
|
||
<div class="dashboard-charts">
|
||
<div class="chart-container">
|
||
<h3>Support Level Distribution</h3>
|
||
<canvas id="support-chart"></canvas>
|
||
</div>
|
||
<div class="chart-container">
|
||
<h3>Sign Sizes Requested</h3>
|
||
<canvas id="sign-sizes-chart"></canvas>
|
||
</div>
|
||
<div class="chart-container chart-full-width">
|
||
<h3>Daily Entries (Last 30 Days)</h3>
|
||
<canvas id="entries-chart"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- NocoDB Links Section -->
|
||
<section id="nocodb-links" class="admin-section" style="display: none;">
|
||
<h2>NocoDB Database Links</h2>
|
||
<p>Quick access to all NocoDB database views and sheets for data management.</p>
|
||
|
||
<div class="nocodb-info">
|
||
<div class="info-box">
|
||
<h4>💡 About NocoDB</h4>
|
||
<p>
|
||
Everything inside Map can be automated through NocoDB and <strong>N8N</strong>, which can be accessed as applications through the Homepage. You can expand the fucntions of Map extensively, including build whole new application functions.
|
||
</p>
|
||
<p>
|
||
NocoDB is the database backend that powers this application. Use these links to directly access and manage your data in the NocoDB interface.<br>
|
||
<a href="https://nocodb.com/docs/product-docs" target="_blank" rel="noopener" class="btn btn-link">NocoDB Documentation</a>
|
||
<a href="https://docs.n8n.io/" target="_blank" rel="noopener" class="btn btn-link">N8N Documentation</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="nocodb-links-container">
|
||
<div class="nocodb-cards">
|
||
<div class="nocodb-card">
|
||
<div class="nocodb-card-header">
|
||
<h3>📊 Data View</h3>
|
||
<span class="nocodb-card-badge">Primary</span>
|
||
</div>
|
||
<p>Main database view for all location and campaign data</p>
|
||
<a href="#" id="admin-nocodb-view-link" class="btn btn-primary" target="_blank">
|
||
Open Data View
|
||
</a>
|
||
</div>
|
||
|
||
<div class="nocodb-card">
|
||
<div class="nocodb-card-header">
|
||
<h3>👥 Login Sheet</h3>
|
||
<span class="nocodb-card-badge">Users</span>
|
||
</div>
|
||
<p>Manage user accounts and authentication settings</p>
|
||
<a href="#" id="admin-nocodb-login-link" class="btn btn-secondary" target="_blank">
|
||
Open Login Sheet
|
||
</a>
|
||
</div>
|
||
|
||
<div class="nocodb-card">
|
||
<div class="nocodb-card-header">
|
||
<h3>⚙️ Settings Sheet</h3>
|
||
<span class="nocodb-card-badge">Config</span>
|
||
</div>
|
||
<p>Configure application settings and preferences</p>
|
||
<a href="#" id="admin-nocodb-settings-link" class="btn btn-secondary" target="_blank">
|
||
Open Settings Sheet
|
||
</a>
|
||
</div>
|
||
|
||
<div class="nocodb-card">
|
||
<div class="nocodb-card-header">
|
||
<h3>📅 Shifts Sheet</h3>
|
||
<span class="nocodb-card-badge">Schedule</span>
|
||
</div>
|
||
<p>Manage volunteer shifts and scheduling</p>
|
||
<a href="#" id="admin-nocodb-shifts-link" class="btn btn-secondary" target="_blank">
|
||
Open Shifts Sheet
|
||
</a>
|
||
</div>
|
||
|
||
<div class="nocodb-card">
|
||
<div class="nocodb-card-header">
|
||
<h3>📝 Shift Signups</h3>
|
||
<span class="nocodb-card-badge">Volunteers</span>
|
||
</div>
|
||
<p>View and manage volunteer shift signups</p>
|
||
<a href="#" id="admin-nocodb-signups-link" class="btn btn-secondary" target="_blank">
|
||
Open Shift Signups
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Listmonk Links Section -->
|
||
<section id="listmonk-links" class="admin-section" style="display: none;">
|
||
<h2>Listmonk Email Management Links</h2>
|
||
<p>Quick access to all Listmonk email management interfaces for campaign communications.</p>
|
||
|
||
<div class="nocodb-info">
|
||
<div class="info-box">
|
||
<h4>📧 About Listmonk</h4>
|
||
<p>
|
||
Listmonk is the email marketing platform that powers campaign communications. Use these links to directly access and manage your email lists, campaigns, and subscribers through the Listmonk interface.
|
||
</p>
|
||
<p>
|
||
The Map application automatically syncs data to Listmonk email lists based on support levels and sign requests.<br>
|
||
<a href="https://listmonk.app/docs/" target="_blank" rel="noopener" class="btn btn-link">Listmonk Documentation</a>
|
||
<a href="https://github.com/knadh/listmonk" target="_blank" rel="noopener" class="btn btn-link">GitHub Repository</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="nocodb-links-container">
|
||
<div class="nocodb-cards">
|
||
<div class="nocodb-card">
|
||
<div class="nocodb-card-header">
|
||
<h3>🏠 Dashboard</h3>
|
||
<span class="nocodb-card-badge">Main</span>
|
||
</div>
|
||
<p>Main Listmonk dashboard with overview and analytics</p>
|
||
<a href="#" id="admin-listmonk-admin-link" class="btn btn-primary" target="_blank">
|
||
Open Dashboard
|
||
</a>
|
||
</div>
|
||
|
||
<div class="nocodb-card">
|
||
<div class="nocodb-card-header">
|
||
<h3>📋 Email Lists</h3>
|
||
<span class="nocodb-card-badge">Lists</span>
|
||
</div>
|
||
<p>Manage email lists and subscriber segments</p>
|
||
<a href="#" id="admin-listmonk-lists-link" class="btn btn-secondary" target="_blank">
|
||
Open Lists
|
||
</a>
|
||
</div>
|
||
|
||
<div class="nocodb-card">
|
||
<div class="nocodb-card-header">
|
||
<h3>📧 Campaigns</h3>
|
||
<span class="nocodb-card-badge">Email</span>
|
||
</div>
|
||
<p>Create and manage email campaigns</p>
|
||
<a href="#" id="admin-listmonk-campaigns-link" class="btn btn-secondary" target="_blank">
|
||
Open Campaigns
|
||
</a>
|
||
</div>
|
||
|
||
<div class="nocodb-card">
|
||
<div class="nocodb-card-header">
|
||
<h3>👤 Subscribers</h3>
|
||
<span class="nocodb-card-badge">People</span>
|
||
</div>
|
||
<p>View and manage email subscribers</p>
|
||
<a href="#" id="admin-listmonk-subscribers-link" class="btn btn-secondary" target="_blank">
|
||
Open Subscribers
|
||
</a>
|
||
</div>
|
||
|
||
<div class="nocodb-card">
|
||
<div class="nocodb-card-header">
|
||
<h3>⚙️ Settings</h3>
|
||
<span class="nocodb-card-badge">Config</span>
|
||
</div>
|
||
<p>Configure Listmonk settings and SMTP</p>
|
||
<a href="#" id="admin-listmonk-settings-link" class="btn btn-secondary" target="_blank">
|
||
Open Settings
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Start Location Section -->
|
||
<section id="start-location" class="admin-section">
|
||
<h2>Map Start Location</h2>
|
||
<p>Set the default center point and zoom level for the map when users first load the application.</p>
|
||
|
||
<div class="admin-map-container">
|
||
<div id="admin-map" class="admin-map"></div>
|
||
|
||
<div class="location-controls">
|
||
<div class="form-group">
|
||
<label for="start-lat">Latitude</label>
|
||
<input type="number" id="start-lat" step="0.000001" min="-90" max="90">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="start-lng">Longitude</label>
|
||
<input type="number" id="start-lng" step="0.000001" min="-180" max="180">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="start-zoom">Zoom Level</label>
|
||
<input type="number" id="start-zoom" min="2" max="19" step="1">
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button id="use-current-view" class="btn btn-secondary">
|
||
Use Current Map View
|
||
</button>
|
||
<button id="save-start-location" class="btn btn-primary">
|
||
Save Start Location
|
||
</button>
|
||
</div>
|
||
|
||
<div class="help-text">
|
||
<p>💡 Tip: Navigate the map to your desired location and zoom level, then click "Use Current Map View" to capture the coordinates.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Walk Sheet Section -->
|
||
<section id="walk-sheet" class="admin-section" style="display: none;">
|
||
<h2>Walk Sheet Configuration</h2>
|
||
<p>Design and configure printable walk sheets for door-to-door canvassing.</p>
|
||
|
||
<div class="walk-sheet-container">
|
||
<div class="walk-sheet-config">
|
||
<h3>Sheet Information</h3>
|
||
|
||
<div class="form-group">
|
||
<label for="walk-sheet-title">Sheet Title</label>
|
||
<input type="text" id="walk-sheet-title" placeholder="Campaign Walk Sheet">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="walk-sheet-subtitle">Subtitle</label>
|
||
<input type="text" id="walk-sheet-subtitle" placeholder="Door-to-Door Canvassing Form">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="walk-sheet-footer">Footer Text</label>
|
||
<textarea id="walk-sheet-footer" rows="3" placeholder="Contact info, legal text, etc."></textarea>
|
||
</div>
|
||
|
||
<h3>QR Codes</h3>
|
||
<p class="help-text-inline">Add up to 3 QR codes for quick access to digital resources.</p>
|
||
|
||
<!-- QR Code 1 -->
|
||
<div class="qr-code-group">
|
||
<h4>QR Code 1</h4>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="qr-code-1-url">URL</label>
|
||
<input type="url" id="qr-code-1-url" placeholder="https://example.com/signup">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="qr-code-1-label">Label</label>
|
||
<input type="text" id="qr-code-1-label" placeholder="Sign Up">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- QR Code 2 -->
|
||
<div class="qr-code-group">
|
||
<h4>QR Code 2</h4>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="qr-code-2-url">URL</label>
|
||
<input type="url" id="qr-code-2-url" placeholder="https://example.com/donate">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="qr-code-2-label">Label</label>
|
||
<input type="text" id="qr-code-2-label" placeholder="Donate">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- QR Code 3 -->
|
||
<div class="qr-code-group">
|
||
<h4>QR Code 3</h4>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="qr-code-3-url">URL</label>
|
||
<input type="url" id="qr-code-3-url" placeholder="https://example.com/volunteer">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="qr-code-3-label">Label</label>
|
||
<input type="text" id="qr-code-3-label" placeholder="Volunteer">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button id="save-walk-sheet" class="btn btn-primary">
|
||
Save Configuration
|
||
</button>
|
||
<button id="print-walk-sheet" class="btn btn-secondary">
|
||
🖨️ Print Sheet
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="walk-sheet-preview">
|
||
<h3>Preview</h3>
|
||
<div class="preview-controls">
|
||
<span class="preview-info">8.5" x 11" format</span>
|
||
</div>
|
||
<div id="walk-sheet-preview-content" class="walk-sheet-page">
|
||
<!-- Preview content will be generated here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Shifts Section -->
|
||
<section id="shifts" class="admin-section" style="display: none;">
|
||
<h2>Shift Management</h2>
|
||
<p>Create and manage volunteer shifts.</p>
|
||
|
||
<div class="shifts-admin-container">
|
||
<div class="shift-form">
|
||
<h3>Create New Shift</h3>
|
||
<form id="shift-form">
|
||
<div class="form-group">
|
||
<label for="shift-title">Title</label>
|
||
<input type="text" id="shift-title" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="shift-description">Description</label>
|
||
<textarea id="shift-description" rows="3"></textarea>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="shift-date">Date</label>
|
||
<input type="date" id="shift-date" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="shift-start">Start Time</label>
|
||
<input type="time" id="shift-start" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="shift-end">End Time</label>
|
||
<input type="time" id="shift-end" required>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="shift-location">Location</label>
|
||
<input type="text" id="shift-location">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="shift-max-volunteers">Max Volunteers</label>
|
||
<input type="number" id="shift-max-volunteers" min="1" required>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">Create Shift</button>
|
||
<button type="button" class="btn btn-secondary" id="clear-shift-form">Clear</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="shifts-list">
|
||
<h3>Existing Shifts</h3>
|
||
<div id="admin-shifts-list">
|
||
<!-- Shifts will be loaded here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Shift User Management Modal -->
|
||
<div id="shift-user-modal" class="modal" style="display: none;">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3>Manage Volunteers</h3>
|
||
<button class="btn btn-secondary btn-sm" id="close-user-modal">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="shift-info">
|
||
<h4 id="modal-shift-title"></h4>
|
||
<p id="modal-shift-details"></p>
|
||
</div>
|
||
|
||
<div class="add-user-section">
|
||
<h4>Add Volunteer</h4>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="user-select">Select User:</label>
|
||
<select id="user-select" style="width: 100%;">
|
||
<option value="">Select a user...</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<button type="button" class="btn btn-primary" id="add-user-btn">Add User</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="current-volunteers-section">
|
||
<h4>Current Volunteers</h4>
|
||
<div class="volunteer-actions-header">
|
||
<button type="button" class="btn btn-primary btn-sm" id="email-shift-details-btn">
|
||
📧 Email Shift Details
|
||
</button>
|
||
</div>
|
||
<div id="current-volunteers-list">
|
||
<!-- Current volunteers will be loaded here -->
|
||
</div>
|
||
|
||
<!-- Shift Email Progress Container -->
|
||
<div id="shift-email-progress-container" class="email-progress-container">
|
||
<div class="progress-header">
|
||
<div class="progress-title">Sending Shift Details...</div>
|
||
<div class="progress-stats">
|
||
<div class="progress-stat success">
|
||
<span>✓</span>
|
||
<span id="shift-success-count">0</span> sent
|
||
</div>
|
||
<div class="progress-stat error">
|
||
<span>✗</span>
|
||
<span id="shift-error-count">0</span> failed
|
||
</div>
|
||
<div class="progress-stat pending">
|
||
<span>⏳</span>
|
||
<span id="shift-pending-count">0</span> pending
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="progress-bar-container">
|
||
<div class="progress-bar" id="shift-email-progress-bar">
|
||
<div class="progress-text" id="shift-progress-text">0%</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="email-status-list" id="shift-email-status-list">
|
||
<!-- Status items will be added dynamically -->
|
||
</div>
|
||
|
||
<div class="progress-actions">
|
||
<button type="button" class="progress-close-btn" id="close-shift-progress-btn" style="display: none;">
|
||
Close
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Users Section -->
|
||
<section id="users" class="admin-section" style="display: none;">
|
||
<h2>User Management</h2>
|
||
<p>Create and manage user accounts for the application.</p>
|
||
|
||
<div class="users-admin-container">
|
||
<div class="user-form">
|
||
<h3>Create New User</h3>
|
||
<form id="create-user-form">
|
||
<div class="form-group">
|
||
<label for="user-email">Email</label>
|
||
<input type="email" id="user-email" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="user-name">Name</label>
|
||
<input type="text" id="user-name" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="user-phone">Phone Number</label>
|
||
<input type="tel" id="user-phone" placeholder="+1 (555) 123-4567">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="user-password">Password</label>
|
||
<input type="password" id="user-password" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="user-type">User Type</label>
|
||
<select id="user-type" required>
|
||
<option value="user">Regular User</option>
|
||
<option value="temp">Temporary User</option>
|
||
<option value="admin">Admin</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group" id="expiration-group" style="display: none;">
|
||
<label for="user-expire-days">Expires After (days)</label>
|
||
<input type="number" id="user-expire-days" min="1" max="365" value="30">
|
||
<small class="help-text">Account will auto-delete after this many days</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>
|
||
<input type="checkbox" id="user-is-admin">
|
||
Is Admin (Legacy - use User Type instead)
|
||
</label>
|
||
</div>
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">Create User</button>
|
||
<button type="button" id="clear-user-form" class="btn btn-secondary">Clear</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="users-list">
|
||
<div class="users-list-header">
|
||
<h3>All Users</h3>
|
||
<button type="button" class="btn btn-primary btn-sm" id="email-all-users-btn">
|
||
📧 Email All Users
|
||
</button>
|
||
</div>
|
||
<!-- User table will be dynamically inserted here -->
|
||
<p id="users-loading" class="loading-message">Loading users...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Email All Users Modal -->
|
||
<div id="email-users-modal" class="modal" style="display: none;">
|
||
<div class="modal-content modal-large">
|
||
<div class="modal-header">
|
||
<h3>Email All Users</h3>
|
||
<button class="btn btn-secondary btn-sm" id="close-email-modal">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="email-form">
|
||
<form id="email-users-form">
|
||
<div class="form-group">
|
||
<label for="email-subject">Subject</label>
|
||
<input type="text" id="email-subject" required placeholder="Enter email subject">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="email-content">Message</label>
|
||
<div class="rich-text-toolbar">
|
||
<button type="button" class="toolbar-btn" data-command="bold" title="Bold">
|
||
<strong>B</strong>
|
||
</button>
|
||
<button type="button" class="toolbar-btn" data-command="italic" title="Italic">
|
||
<em>I</em>
|
||
</button>
|
||
<button type="button" class="toolbar-btn" data-command="underline" title="Underline">
|
||
<u>U</u>
|
||
</button>
|
||
<button type="button" class="toolbar-btn" data-command="insertUnorderedList" title="Bullet List">
|
||
• List
|
||
</button>
|
||
<button type="button" class="toolbar-btn" data-command="insertOrderedList" title="Numbered List">
|
||
1. List
|
||
</button>
|
||
<button type="button" class="toolbar-btn" data-command="createLink" title="Insert Link">
|
||
🔗 Link
|
||
</button>
|
||
</div>
|
||
<div id="email-content" class="rich-text-editor" contenteditable="true"
|
||
placeholder="Type your message here..."
|
||
style="min-height: 200px; border: 1px solid #ddd; padding: 15px; border-radius: 4px; background: white;">
|
||
</div>
|
||
<small class="help-text">Use the toolbar above to format your message</small>
|
||
</div>
|
||
|
||
<div class="email-preview-section">
|
||
<div class="form-group">
|
||
<label>
|
||
<input type="checkbox" id="show-preview" />
|
||
Show email preview
|
||
</label>
|
||
</div>
|
||
|
||
<div id="email-preview" class="email-preview" style="display: none;">
|
||
<h4>Email Preview:</h4>
|
||
<div class="email-preview-content">
|
||
<div class="preview-header">
|
||
<strong>Subject:</strong> <span id="preview-subject">Your subject will appear here</span>
|
||
</div>
|
||
<div class="preview-body" id="preview-body">
|
||
Your message will appear here
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="email-recipients-info">
|
||
<p><strong>Recipients:</strong> <span id="recipients-count">Loading...</span> users will receive this email</p>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary" id="send-email-btn">
|
||
📧 Send Email to All Users
|
||
</button>
|
||
<button type="button" class="btn btn-secondary" id="cancel-email-btn">
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</form>
|
||
|
||
<!-- Email Progress Container -->
|
||
<div id="email-progress-container" class="email-progress-container">
|
||
<div class="progress-header">
|
||
<div class="progress-title">Sending Emails...</div>
|
||
<div class="progress-stats">
|
||
<div class="progress-stat success">
|
||
<span>✓</span>
|
||
<span id="success-count">0</span> sent
|
||
</div>
|
||
<div class="progress-stat error">
|
||
<span>✗</span>
|
||
<span id="error-count">0</span> failed
|
||
</div>
|
||
<div class="progress-stat pending">
|
||
<span>⏳</span>
|
||
<span id="pending-count">0</span> pending
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="progress-bar-container">
|
||
<div class="progress-bar" id="email-progress-bar">
|
||
<div class="progress-text" id="progress-text">0%</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="email-status-list" id="email-status-list">
|
||
<!-- Status items will be added dynamically -->
|
||
</div>
|
||
|
||
<div class="progress-actions">
|
||
<button type="button" class="progress-close-btn" id="close-progress-btn" style="display: none;">
|
||
Close
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Map Cuts Section -->
|
||
<section id="cuts" class="admin-section" style="display: none;">
|
||
<h2>Map Cuts</h2>
|
||
<p>Create and manage polygon overlays for the map. Cuts can be used to define areas like wards, neighborhoods, or custom regions.</p>
|
||
|
||
<div class="cuts-container">
|
||
<!-- Top Section with Existing Cuts and Form -->
|
||
<div class="cuts-top-section">
|
||
<!-- Existing Cuts List - Moved to Top -->
|
||
<div class="cuts-list-section">
|
||
<div class="cuts-management-panel">
|
||
<div class="panel-header">
|
||
<h3 class="panel-title">Existing Cuts</h3>
|
||
<div class="panel-actions">
|
||
<button id="refresh-cuts-btn" class="btn btn-secondary btn-sm">Refresh</button>
|
||
<button id="export-cuts-btn" class="btn btn-secondary btn-sm">Export All</button>
|
||
<label for="import-cuts-file" class="btn btn-secondary btn-sm" style="margin: 0;">
|
||
Import
|
||
<input type="file" id="import-cuts-file" accept=".json" style="display: none;">
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="panel-content">
|
||
<div class="cuts-filters">
|
||
<input type="text" id="cuts-search" placeholder="Search cuts..." class="form-control">
|
||
<select id="cuts-category-filter" class="form-control">
|
||
<option value="">All Categories</option>
|
||
<option value="Custom">Custom</option>
|
||
<option value="Ward">Ward</option>
|
||
<option value="Neighborhood">Neighborhood</option>
|
||
<option value="District">District</option>
|
||
</select>
|
||
<select id="cuts-per-page" class="form-control">
|
||
<option value="5" selected>5 per page</option>
|
||
<option value="10">10 per page</option>
|
||
<option value="20">20 per page</option>
|
||
<option value="50">50 per page</option>
|
||
<option value="100">100 per page</option>
|
||
</select>
|
||
</div>
|
||
<div id="cuts-list" class="cuts-list">
|
||
<!-- Cuts will be populated here -->
|
||
</div>
|
||
<!-- Pagination Controls (will be dynamically created after cuts-list) -->
|
||
<div id="cuts-pagination" class="cuts-pagination" style="display: none;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cut Properties - Streamlined Card -->
|
||
<div class="cuts-form-section">
|
||
<div class="cuts-management-panel cuts-form-card">
|
||
<div class="panel-header">
|
||
<h3 class="panel-title" id="cut-form-title">Cut Properties</h3>
|
||
<div class="panel-actions">
|
||
<button id="start-drawing-btn" class="btn btn-primary btn-sm">Start Drawing</button>
|
||
</div>
|
||
</div>
|
||
<div class="panel-content">
|
||
<form id="cut-form" class="cut-form cut-form-compact">
|
||
<!-- Hidden fields moved to prevent duplicates -->
|
||
<input type="hidden" id="cut-id" name="id">
|
||
<input type="hidden" id="cut-geojson" name="geojson">
|
||
<input type="hidden" id="cut-bounds" name="bounds">
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label for="cut-name">Name *</label>
|
||
<input type="text" id="cut-name" name="name" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="cut-category">Category</label>
|
||
<select id="cut-category" name="category">
|
||
<option value="Custom">Custom</option>
|
||
<option value="Ward">Ward</option>
|
||
<option value="Neighborhood">Neighborhood</option>
|
||
<option value="District">District</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="cut-description">Description</label>
|
||
<textarea id="cut-description" name="description" rows="2"></textarea>
|
||
</div>
|
||
|
||
<div class="form-options">
|
||
<div class="form-group checkbox-group">
|
||
<input type="checkbox" id="cut-public" name="is_public" checked>
|
||
<label for="cut-public">Visible on public map</label>
|
||
</div>
|
||
|
||
<div class="form-group checkbox-group">
|
||
<input type="checkbox" id="cut-official" name="is_official">
|
||
<label for="cut-official">Official cut</label>
|
||
</div>
|
||
|
||
<div class="form-group checkbox-group">
|
||
<input type="checkbox" id="show-locations-on-map" name="show_locations">
|
||
<label for="show-locations-on-map">Show locations on map</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" id="save-cut-btn" class="btn btn-success" disabled>Save Cut</button>
|
||
<button type="button" id="reset-form-btn" class="btn btn-secondary">Reset</button>
|
||
<button type="button" id="cancel-edit-btn" class="btn btn-secondary" style="display: none;">Cancel Edit</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cut Location Management - Moved Above Map -->
|
||
<div class="cuts-location-section" id="cut-location-management" style="display: none;">
|
||
<div class="cuts-management-panel">
|
||
<div class="panel-content">
|
||
<!-- Location Filters -->
|
||
<div class="location-filters">
|
||
<h4>Filter Locations</h4>
|
||
<div class="filter-row">
|
||
<div class="filter-group">
|
||
<label for="support-level-filter">Support Level:</label>
|
||
<select id="support-level-filter" class="form-control">
|
||
<option value="">All Levels</option>
|
||
<option value="1">Level 1 (Strong Support)</option>
|
||
<option value="2">Level 2 (Lean Support)</option>
|
||
<option value="3">Level 3 (Lean Opposition)</option>
|
||
<option value="4">Level 4 (Strong Opposition)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label for="sign-status-filter">Lawn Sign:</label>
|
||
<select id="sign-status-filter" class="form-control">
|
||
<option value="">All</option>
|
||
<option value="true">Has Sign</option>
|
||
<option value="false">No Sign</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label for="sign-size-filter">Sign Size:</label>
|
||
<select id="sign-size-filter" class="form-control">
|
||
<option value="">All Sizes</option>
|
||
<option value="Regular">Regular</option>
|
||
<option value="Large">Large</option>
|
||
<option value="Unsure">Unsure</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label for="contact-filter">Contact Info:</label>
|
||
<select id="contact-filter" class="form-control">
|
||
<option value="">All</option>
|
||
<option value="email">Has Email</option>
|
||
<option value="phone">Has Phone</option>
|
||
<option value="both">Has Both</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<button id="apply-filters" class="btn btn-primary">Apply Filters</button>
|
||
<button id="clear-filters" class="btn btn-secondary">Clear</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cut Statistics -->
|
||
<div class="cut-statistics" id="cut-statistics" style="display: none;">
|
||
<h4>Statistics</h4>
|
||
<div class="stats-grid">
|
||
<div class="stat-item">
|
||
<span class="stat-label">Total Locations:</span>
|
||
<span class="stat-value" id="total-locations">0</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-label">Support Level 1:</span>
|
||
<span class="stat-value" id="support-1">0</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-label">Support Level 2:</span>
|
||
<span class="stat-value" id="support-2">0</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-label">Has Lawn Signs:</span>
|
||
<span class="stat-value" id="has-signs">0</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-label">Has Email:</span>
|
||
<span class="stat-value" id="has-email">0</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-label">Has Phone:</span>
|
||
<span class="stat-value" id="has-phone">0</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cut Settings -->
|
||
<div class="cut-settings">
|
||
<h4>Cut Settings</h4>
|
||
<div class="setting-row">
|
||
<div class="setting-group">
|
||
<label>
|
||
<input type="checkbox" id="show-locations-toggle" checked>
|
||
Show locations on map
|
||
</label>
|
||
</div>
|
||
<div class="setting-group">
|
||
<label>
|
||
<input type="checkbox" id="export-enabled-toggle" checked>
|
||
Enable data export
|
||
</label>
|
||
</div>
|
||
<div class="setting-group">
|
||
<label for="assigned-to">Assigned to:</label>
|
||
<input type="text" id="assigned-to" class="form-control" placeholder="Volunteer name/email">
|
||
</div>
|
||
<div class="setting-group">
|
||
<label for="completion-percentage">Completion:</label>
|
||
<input type="number" id="completion-percentage" class="form-control" min="0" max="100" placeholder="0">
|
||
<span>%</span>
|
||
</div>
|
||
<div class="setting-group">
|
||
<button id="save-cut-settings" class="btn btn-success">Save Settings</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Location Management Footer Actions -->
|
||
<div class="panel-footer">
|
||
<h3 class="panel-title">Location Management</h3>
|
||
<div class="panel-actions">
|
||
<button id="toggle-location-visibility" class="btn btn-primary btn-sm">Show Locations</button>
|
||
<button id="export-cut-locations" class="btn btn-success btn-sm">Export Data</button>
|
||
<button id="print-cut-view" class="btn btn-secondary btn-sm">Print View</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Map and Drawing Controls -->
|
||
<div class="cuts-map-section">
|
||
<div id="cuts-map" class="admin-map"></div>
|
||
|
||
<!-- Drawing Toolbar -->
|
||
<div id="cut-drawing-toolbar" class="cut-drawing-toolbar">
|
||
<div class="toolbar-content">
|
||
<div class="vertex-count" id="vertex-count">0</div>
|
||
|
||
<div class="style-controls">
|
||
<div class="color-control">
|
||
<label>Color:</label>
|
||
<input type="color" id="toolbar-color" value="#3388ff">
|
||
</div>
|
||
<div class="opacity-control">
|
||
<label>Opacity:</label>
|
||
<input type="range" id="toolbar-opacity" min="0" max="1" step="0.05" value="0.3">
|
||
<span class="opacity-value" id="toolbar-opacity-display">30%</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toolbar-buttons">
|
||
<button type="button" id="finish-cut-btn" class="primary" disabled>Finish</button>
|
||
<button type="button" id="undo-vertex-btn" class="secondary" disabled>Undo</button>
|
||
<button type="button" id="clear-vertices-btn" class="secondary" disabled>Clear</button>
|
||
<button type="button" id="cancel-cut-btn" class="danger">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Convert Data Section -->
|
||
<section id="convert-data" class="admin-section" style="display: none;">
|
||
<h2>Convert Data</h2>
|
||
<p>Upload a CSV file containing addresses to geocode and import into the map.</p>
|
||
|
||
<div class="data-convert-container">
|
||
<div class="upload-section" id="upload-section">
|
||
<h3>CSV Upload</h3>
|
||
<form id="csv-upload-form">
|
||
<div class="upload-area" id="upload-area">
|
||
<div class="upload-icon">📁</div>
|
||
<p>Drag and drop your CSV file here or click to browse</p>
|
||
<input type="file" id="csv-file-input" accept=".csv" style="display: none;">
|
||
<button type="button" class="btn btn-primary" id="browse-btn">Choose File</button>
|
||
</div>
|
||
|
||
<div class="file-info" id="file-info" style="display: none;">
|
||
<p><strong>Selected file:</strong> <span id="file-name"></span></p>
|
||
<p><strong>Size:</strong> <span id="file-size"></span></p>
|
||
</div>
|
||
|
||
<div class="csv-requirements">
|
||
<h4>CSV Requirements:</h4>
|
||
<div class="requirements-section">
|
||
<h5>Required Column:</h5>
|
||
<ul>
|
||
<li><strong>address</strong> - The street address to geocode (case-insensitive)</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="requirements-section">
|
||
<h5>Optional Columns (any of these names will work):</h5>
|
||
<div class="field-mapping-grid">
|
||
<div class="field-group">
|
||
<strong>First Name:</strong>
|
||
<ul>
|
||
<li>first name</li>
|
||
<li>firstname</li>
|
||
<li>first_name</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="field-group">
|
||
<strong>Last Name:</strong>
|
||
<ul>
|
||
<li>last name</li>
|
||
<li>lastname</li>
|
||
<li>last_name</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="field-group">
|
||
<strong>Email:</strong>
|
||
<ul>
|
||
<li>email</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="field-group">
|
||
<strong>Phone:</strong>
|
||
<ul>
|
||
<li>phone</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="field-group">
|
||
<strong>Unit Number:</strong>
|
||
<ul>
|
||
<li>unit</li>
|
||
<li>unit number</li>
|
||
<li>unit_number</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="field-group">
|
||
<strong>Support Level (1-4):</strong>
|
||
<ul>
|
||
<li>support level</li>
|
||
<li>support_level</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="field-group">
|
||
<strong>Sign (true/false):</strong>
|
||
<ul>
|
||
<li>sign</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="field-group">
|
||
<strong>Sign Size (Regular, Large, Unsure)</strong>
|
||
<ul>
|
||
<li>sign size</li>
|
||
<li>sign_size</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="field-group">
|
||
<strong>Notes:</strong>
|
||
<ul>
|
||
<li>notes</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="requirements-section">
|
||
<h5>File Specifications:</h5>
|
||
<ul>
|
||
<li>Maximum file size: 10MB</li>
|
||
<li>Column names are case-insensitive</li>
|
||
<li>Extra columns will be ignored</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="requirements-section">
|
||
<h5>Example CSV Format:</h5>
|
||
<pre class="csv-example">address,first name,last name,email,phone,support level,notes
|
||
"123 Main St, Edmonton, AB",John,Doe,john@example.com,780-555-0123,2,Interested in campaign
|
||
"456 Oak Ave, Edmonton, AB",Jane,Smith,jane@example.com,780-555-0456,1,Strong supporter</pre>
|
||
<p style="margin-top: 10px;">
|
||
<a href="data:text/csv;charset=utf-8,address%2Cfirst%20name%2Clast%20name%2Cemail%2Cphone%2Csupport%20level%2Cnotes%0A%22123%20Main%20St%2C%20Edmonton%2C%20AB%22%2CJohn%2CDoe%2Cjohn%40example.com%2C780-555-0123%2C2%2CInterested%20in%20campaign%0A%22456%20Oak%20Ave%2C%20Edmonton%2C%20AB%22%2CJane%2CSmith%2Cjane%40example.com%2C780-555-0456%2C1%2CStrong%20supporter"
|
||
download="sample-import.csv"
|
||
class="btn btn-secondary btn-sm">
|
||
📄 Download Sample CSV
|
||
</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary" id="process-csv-btn" disabled>
|
||
Process CSV
|
||
</button>
|
||
<button type="button" class="btn btn-secondary" id="clear-upload-btn">
|
||
Clear
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="processing-section" id="processing-section" style="display: none;">
|
||
<h3>Processing Progress</h3>
|
||
|
||
<div class="progress-bar-container">
|
||
<div class="progress-bar">
|
||
<div class="progress-bar-fill" id="progress-bar-fill"></div>
|
||
</div>
|
||
<p class="progress-text"><span id="progress-current">0</span> / <span id="progress-total">0</span> addresses processed</p>
|
||
</div>
|
||
|
||
<div class="processing-status" id="processing-status">
|
||
<p id="current-address"></p>
|
||
</div>
|
||
|
||
<div class="results-preview" id="results-preview" style="display: none;">
|
||
<h4>Preview Results</h4>
|
||
<div class="results-map" id="results-map"></div>
|
||
<div class="results-table-container">
|
||
<table class="results-table" id="results-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Status</th>
|
||
<th>Original Address</th>
|
||
<th>Geocoded Address</th>
|
||
<th>Coordinates</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="results-tbody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="processing-actions" id="processing-actions" style="display: none;">
|
||
<button type="button" class="btn btn-success" id="save-results-btn">
|
||
Add Data to Map
|
||
</button>
|
||
<button type="button" class="btn btn-secondary" id="new-upload-btn">
|
||
Upload New File
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Email Lists (Listmonk) Section -->
|
||
<section id="listmonk" class="admin-section" style="display: none;">
|
||
<h2>Email List Management (Listmonk)</h2>
|
||
<p>Manage email list synchronization with Listmonk for campaign communications.</p>
|
||
|
||
<div class="listmonk-container">
|
||
<div class="listmonk-stats">
|
||
<h3>Sync Status</h3>
|
||
<div id="sync-status-display">
|
||
<div class="status-row">
|
||
<span>Connection:</span>
|
||
<span id="connection-status">Checking...</span>
|
||
</div>
|
||
<div class="status-row">
|
||
<span>Auto-sync:</span>
|
||
<span id="autosync-status">Checking...</span>
|
||
</div>
|
||
<div class="status-row">
|
||
<span>Last Error:</span>
|
||
<span id="last-error">None</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="listmonk-actions">
|
||
<h3>Sync Actions</h3>
|
||
<p>Force synchronization of all data to Listmonk email lists:</p>
|
||
<div class="sync-buttons">
|
||
<button id="sync-locations-btn" class="btn btn-primary">
|
||
<span>📍</span> Sync All Locations
|
||
</button>
|
||
<button id="sync-users-btn" class="btn btn-primary">
|
||
<span>👤</span> Sync All Users
|
||
</button>
|
||
<button id="sync-all-btn" class="btn btn-success">
|
||
<span>🔄</span> Sync Everything
|
||
</button>
|
||
<button id="refresh-status-btn" class="btn btn-secondary">
|
||
<span>🔍</span> Refresh Status
|
||
</button>
|
||
<button id="test-connection-btn" class="btn btn-warning">
|
||
<span>🔗</span> Test Connection
|
||
</button>
|
||
<button id="reinitialize-lists-btn" class="btn btn-info">
|
||
<span>🛠️</span> Reinitialize Lists
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="sync-progress" class="sync-progress" style="display: none;">
|
||
<h3>Sync Progress</h3>
|
||
<div class="progress-bar-container">
|
||
<div class="progress-bar">
|
||
<div class="progress-bar-fill" id="sync-progress-bar"></div>
|
||
</div>
|
||
</div>
|
||
<div id="sync-results"></div>
|
||
</div>
|
||
|
||
<div class="listmonk-info">
|
||
<h3>About Email List Sync</h3>
|
||
<div class="info-grid">
|
||
<div class="info-section">
|
||
<h4>Automatic Lists Created:</h4>
|
||
<ul>
|
||
<li><strong>Map Locations - All:</strong> All contacts with email addresses</li>
|
||
<li><strong>Map Users - All:</strong> All system users</li>
|
||
<li><strong>Support Level 1-4:</strong> Segmented by support level</li>
|
||
<li><strong>Has Campaign Sign:</strong> Contacts with signs</li>
|
||
<li><strong>Wants Campaign Sign:</strong> Contacts wanting signs (from notes)</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<h4>Real-time Sync:</h4>
|
||
<ul>
|
||
<li>New locations automatically sync to appropriate lists</li>
|
||
<li>Updates are reflected immediately in email lists</li>
|
||
<li>Deletions remove contacts from all lists</li>
|
||
<li>Sync failures are logged and shown in status</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<h4>Contact Data Synced:</h4>
|
||
<ul>
|
||
<li>Email, name, phone, address</li>
|
||
<li>Support level, sign status, notes</li>
|
||
<li>Geographic coordinates</li>
|
||
<li>Source tracking (map_location/map_user)</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="info-section">
|
||
<h4>Troubleshooting:</h4>
|
||
<ul>
|
||
<li>Check Listmonk service is running</li>
|
||
<li>Verify credentials in environment variables</li>
|
||
<li>Use "Test Connection" to verify setup</li>
|
||
<li>Check logs for detailed error information</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Status Messages -->
|
||
<div id="status-container" class="status-container"></div>
|
||
</div>
|
||
|
||
<!-- Leaflet JS -->
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||
crossorigin=""></script>
|
||
|
||
<!-- Custom QR Code Implementation -->
|
||
<script>
|
||
// Simple QR Code implementation using our server
|
||
window.QRCode = {
|
||
toCanvas: function(canvas, text, options, callback) {
|
||
if (typeof options === 'function') {
|
||
callback = options;
|
||
options = {};
|
||
}
|
||
|
||
const size = options.width || 200;
|
||
const qrUrl = `/api/qr?text=${encodeURIComponent(text)}&size=${size}`;
|
||
|
||
const img = new Image();
|
||
img.crossOrigin = 'anonymous';
|
||
|
||
img.onload = function() {
|
||
canvas.width = size;
|
||
canvas.height = size;
|
||
|
||
const ctx = canvas.getContext('2d');
|
||
ctx.drawImage(img, 0, 0, size, size);
|
||
|
||
if (callback) callback(null);
|
||
};
|
||
|
||
img.onerror = function() {
|
||
console.error('Failed to load QR code from server');
|
||
|
||
// Fallback: draw a simple placeholder
|
||
canvas.width = size;
|
||
canvas.height = size;
|
||
|
||
const ctx = canvas.getContext('2d');
|
||
ctx.fillStyle = '#ffffff';
|
||
ctx.fillRect(0, 0, size, size);
|
||
|
||
ctx.fillStyle = '#000000';
|
||
ctx.font = '12px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('QR Code', size/2, size/2 - 10);
|
||
ctx.fillText('(Failed)', size/2, size/2 + 10);
|
||
|
||
if (callback) callback(new Error('Failed to load QR code'));
|
||
};
|
||
|
||
img.src = qrUrl;
|
||
}
|
||
};
|
||
|
||
console.log('Local QR Code implementation loaded');
|
||
</script>
|
||
|
||
<!-- Cache Management -->
|
||
<script src="js/cache-manager.js"></script>
|
||
|
||
<!-- Chart.js library -->
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||
|
||
<!-- dom-to-image library for map screenshots -->
|
||
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/dom-to-image@2.6.0/dist/dom-to-image.min.js"></script>
|
||
|
||
<!-- Dashboard JavaScript -->
|
||
<script src="js/dashboard.js"></script>
|
||
|
||
<!-- Admin Cuts JavaScript - Load modules first, then main file -->
|
||
<script src="js/cut-drawing.js"></script>
|
||
<script src="js/cut-location-manager.js"></script>
|
||
<script src="js/cut-print-utils.js"></script>
|
||
<script src="js/admin-cuts-manager.js"></script>
|
||
<script src="js/admin-cuts.js"></script>
|
||
|
||
<!-- Listmonk Status and Admin -->
|
||
<script src="js/listmonk-status.js"></script>
|
||
<script src="js/listmonk-admin.js"></script>
|
||
|
||
<!-- Data Convert JavaScript -->
|
||
<script src="js/data-convert.js"></script>
|
||
|
||
<!-- Modular Admin JavaScript - Load in order of dependencies -->
|
||
<script data-cfasync="false" src="js/admin-core.js"></script> <!-- Core utilities, navigation, viewport -->
|
||
<script data-cfasync="false" src="js/admin-auth.js"></script> <!-- Authentication and user management -->
|
||
<script data-cfasync="false" src="js/admin-map.js"></script> <!-- Map functionality and location management -->
|
||
<script data-cfasync="false" src="js/admin-walksheet.js"></script> <!-- Walk sheet configuration and preview -->
|
||
<script data-cfasync="false" src="js/admin-shifts.js"></script> <!-- Shift management and CRUD operations -->
|
||
<script data-cfasync="false" src="js/admin-shift-volunteers.js"></script> <!-- Volunteer management for shifts -->
|
||
<script data-cfasync="false" src="js/admin-users.js"></script> <!-- User management and CRUD operations -->
|
||
<script data-cfasync="false" src="js/admin-email.js"></script> <!-- Email broadcasting and rich text editor -->
|
||
<script data-cfasync="false" src="js/admin-integration.js"></script> <!-- NocoDB and Listmonk integration links -->
|
||
|
||
<!-- Main Admin Coordinator - Load last to coordinate all modules -->
|
||
<script data-cfasync="false" src="js/admin.js"></script>
|
||
</body>
|
||
</html>
|