freealberta/map/app/public/admin.html

1423 lines
85 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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">
<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-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>
</div>
<div id="cuts-list" class="cuts-list">
<!-- Cuts will be populated here -->
</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>