400 lines
19 KiB
HTML
400 lines
19 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<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="#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>
|
||
</nav>
|
||
<div class="sidebar-footer">
|
||
<div id="mobile-admin-info" class="mobile-admin-info mobile-only"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="admin-content">
|
||
<!-- 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>
|
||
</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="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-password">Password *</label>
|
||
<input type="password" id="user-password" required minlength="6">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="user-name">Name</label>
|
||
<input type="text" id="user-name">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>
|
||
<input type="checkbox" id="user-admin"> Admin Access
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">Create User</button>
|
||
<button type="button" class="btn btn-secondary" id="clear-user-form">Clear</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="users-list">
|
||
<h3>Existing Users</h3>
|
||
<div id="users-table-container">
|
||
<table id="users-table" class="users-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Email</th>
|
||
<th>Name</th>
|
||
<th>Admin</th>
|
||
<th>Created</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="users-table-body">
|
||
<!-- Users will be loaded here -->
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<div id="users-loading" class="loading-message" style="display: none;">
|
||
Loading users...
|
||
</div>
|
||
<div id="users-empty" class="empty-message" style="display: none;">
|
||
No users found.
|
||
</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>
|
||
|
||
<!-- Admin JavaScript -->
|
||
<script src="js/admin.js"></script>
|
||
</body>
|
||
</html>
|