pretix-map 0.1.3__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {pretix_map-0.1.3/pretix_map.egg-info → pretix_map-0.1.4}/PKG-INFO +1 -1
  2. {pretix_map-0.1.3 → pretix_map-0.1.4/pretix_map.egg-info}/PKG-INFO +1 -1
  3. pretix_map-0.1.4/pretix_mapplugin/__init__.py +1 -0
  4. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/management/commands/geocode_existing_orders.py +1 -1
  5. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css +1 -1
  6. pretix_map-0.1.4/pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js +452 -0
  7. pretix_map-0.1.4/pretix_mapplugin/templates/pretix_mapplugin/map_page.html +88 -0
  8. pretix_map-0.1.3/pretix_mapplugin/__init__.py +0 -1
  9. pretix_map-0.1.3/pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js +0 -442
  10. pretix_map-0.1.3/pretix_mapplugin/templates/pretix_mapplugin/map_page.html +0 -68
  11. {pretix_map-0.1.3 → pretix_map-0.1.4}/LICENSE +0 -0
  12. {pretix_map-0.1.3 → pretix_map-0.1.4}/MANIFEST.in +0 -0
  13. {pretix_map-0.1.3 → pretix_map-0.1.4}/README.rst +0 -0
  14. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_map.egg-info/SOURCES.txt +0 -0
  15. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_map.egg-info/dependency_links.txt +0 -0
  16. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_map.egg-info/entry_points.txt +0 -0
  17. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_map.egg-info/requires.txt +0 -0
  18. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_map.egg-info/top_level.txt +0 -0
  19. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/apps.py +0 -0
  20. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/geocoding.py +0 -0
  21. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/locale/de/LC_MESSAGES/django.mo +0 -0
  22. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/locale/de/LC_MESSAGES/django.po +0 -0
  23. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/locale/de_Informal/.gitkeep +0 -0
  24. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/locale/de_Informal/LC_MESSAGES/django.mo +0 -0
  25. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/locale/de_Informal/LC_MESSAGES/django.po +0 -0
  26. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/management/__init__.py +0 -0
  27. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/management/commands/__init__.py +0 -0
  28. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/migrations/0001_initial.py +0 -0
  29. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/migrations/0002_remove_ordergeocodedata_geocoded_timestamp_and_more.py +0 -0
  30. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/migrations/__init__.py +0 -0
  31. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/models.py +0 -0
  32. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/signals.py +0 -0
  33. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/.gitkeep +0 -0
  34. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.Default.css +0 -0
  35. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.css +0 -0
  36. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/layers-2x.png +0 -0
  37. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/layers.png +0 -0
  38. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-icon-2x.png +0 -0
  39. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-icon.png +0 -0
  40. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/images/marker-shadow.png +0 -0
  41. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-heat.js +0 -0
  42. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.esm.js +0 -0
  43. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.esm.js.map +0 -0
  44. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.js +0 -0
  45. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet-src.js.map +0 -0
  46. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.css +0 -0
  47. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.js +0 -0
  48. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.js.map +0 -0
  49. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.markercluster.js +0 -0
  50. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/static/pretix_mapplugin/libs/leaflet-sales-map/leaflet.markercluster.js.map +0 -0
  51. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/tasks.py +0 -0
  52. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/templates/pretix_mapplugin/.gitkeep +0 -0
  53. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/urls.py +0 -0
  54. {pretix_map-0.1.3 → pretix_map-0.1.4}/pretix_mapplugin/views.py +0 -0
  55. {pretix_map-0.1.3 → pretix_map-0.1.4}/pyproject.toml +0 -0
  56. {pretix_map-0.1.3 → pretix_map-0.1.4}/setup.cfg +0 -0
  57. {pretix_map-0.1.3 → pretix_map-0.1.4}/setup.py +0 -0
  58. {pretix_map-0.1.3 → pretix_map-0.1.4}/tests/test_main.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pretix-map
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: An overview map of the catchment area of previous orders. Measured by postcode
5
5
  Author-email: MarkenJaden <jjsch1410@gmail.com>
6
6
  Maintainer-email: MarkenJaden <jjsch1410@gmail.com>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pretix-map
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: An overview map of the catchment area of previous orders. Measured by postcode
5
5
  Author-email: MarkenJaden <jjsch1410@gmail.com>
6
6
  Maintainer-email: MarkenJaden <jjsch1410@gmail.com>
@@ -0,0 +1 @@
1
+ __version__ = "0.1.4"
@@ -131,7 +131,7 @@ class Command(BaseCommand):
131
131
  with scope(organizer=organizer):
132
132
  # --- Get orders ---
133
133
  orders_qs = Order.objects.filter(status=Order.STATUS_PAID).select_related(
134
- 'invoice_address', 'invoice_address__country', 'event'
134
+ 'invoice_address', 'event'
135
135
  )
136
136
 
137
137
  # --- Filter by event ---
@@ -1,7 +1,7 @@
1
1
  .plugin-map-content-wrapper {
2
2
  display: flex;
3
3
  flex-direction: column;
4
- height: calc(100vh - 200px);
4
+ height: calc(100vh - 100px);
5
5
  min-height: 450px;
6
6
  }
7
7
 
@@ -0,0 +1,452 @@
1
+ document.addEventListener('DOMContentLoaded', function () {
2
+ console.log("Sales Map JS Loaded (Cluster Toggle & Heatmap Opts)");
3
+
4
+ // --- L.Icon.Default setup ---
5
+ try {
6
+ delete L.Icon.Default.prototype._getIconUrl;
7
+ L.Icon.Default.mergeOptions({
8
+ iconRetinaUrl: '/static/leaflet/images/marker-icon-2x.png',
9
+ iconUrl: '/static/leaflet/images/marker-icon.png',
10
+ shadowUrl: '/static/leaflet/images/marker-shadow.png',
11
+ iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34],
12
+ tooltipAnchor: [16, -28], shadowSize: [41, 41]
13
+ });
14
+ console.log("Set Leaflet default icon image paths explicitly.");
15
+ } catch (e) {
16
+ console.error("Error setting icon path:", e);
17
+ }
18
+ // --- End L.Icon.Default setup ---
19
+
20
+
21
+ // --- Configuration ---
22
+ const mapContainerId = 'sales-map-container';
23
+ const statusOverlayId = 'map-status-overlay';
24
+ const viewToggleButtonId = 'view-toggle-btn';
25
+ const clusterToggleButtonId = 'cluster-toggle-btn';
26
+ const heatmapOptionsPanelId = 'heatmap-options-panel';
27
+ const initialZoom = 5;
28
+ const defaultMapView = 'pins';
29
+
30
+ // --- Globals & State ---
31
+ let map = null;
32
+ let coordinateData = [];
33
+ let pinLayer = null;
34
+ let heatmapLayer = null;
35
+ let currentView = defaultMapView;
36
+ let dataUrl = null;
37
+ let isClusteringEnabled = true;
38
+ let heatmapOptions = {
39
+ radius: 25, blur: 15, maxZoom: 18, minOpacity: 0.2
40
+ };
41
+
42
+ // --- DOM Elements ---
43
+ const mapElement = document.getElementById(mapContainerId);
44
+ const statusOverlay = document.getElementById(statusOverlayId);
45
+ const viewToggleButton = document.getElementById(viewToggleButtonId);
46
+ const clusterToggleButton = document.getElementById(clusterToggleButtonId);
47
+ const heatmapOptionsPanel = document.getElementById(heatmapOptionsPanelId);
48
+ const heatmapRadiusInput = document.getElementById('heatmap-radius');
49
+ const heatmapBlurInput = document.getElementById('heatmap-blur');
50
+ const heatmapMaxZoomInput = document.getElementById('heatmap-maxZoom');
51
+ const radiusValueSpan = document.getElementById('radius-value');
52
+ const blurValueSpan = document.getElementById('blur-value');
53
+ const maxZoomValueSpan = document.getElementById('maxzoom-value');
54
+
55
+ // --- Status Update Helpers ---
56
+ function updateStatus(message, isError = false) {
57
+ if (statusOverlay) {
58
+ const p = statusOverlay.querySelector('p');
59
+ if (p) {
60
+ p.textContent = message;
61
+ p.className = isError ? 'text-danger' : '';
62
+ }
63
+ statusOverlay.style.display = 'flex';
64
+ } else {
65
+ console.warn("Status overlay element not found.");
66
+ }
67
+ }
68
+
69
+ function hideStatus() {
70
+ if (statusOverlay) {
71
+ statusOverlay.style.display = 'none';
72
+ }
73
+ }
74
+
75
+ // --- End Status Helpers ---
76
+
77
+
78
+ // --- Initialization ---
79
+ function initializeMap() {
80
+ if (!mapElement) {
81
+ console.error(`Map container #${mapContainerId} not found.`);
82
+ return;
83
+ }
84
+ if (!statusOverlay) {
85
+ console.warn("Status overlay element not found.");
86
+ }
87
+ if (!viewToggleButton) console.warn("View toggle button not found.");
88
+ if (!clusterToggleButton) console.warn("Cluster toggle button not found.");
89
+ if (!heatmapOptionsPanel) console.warn("Heatmap options panel not found.");
90
+ if (!heatmapRadiusInput || !heatmapBlurInput || !heatmapMaxZoomInput) console.warn("Heatmap input elements missing.");
91
+
92
+ dataUrl = mapElement.dataset.dataUrl;
93
+ if (!dataUrl) {
94
+ updateStatus("Configuration Error: Missing data source URL.", true);
95
+ return;
96
+ }
97
+ console.log(`Data URL found: ${dataUrl}`);
98
+ updateStatus("Initializing map...");
99
+
100
+ try {
101
+ map = L.map(mapContainerId).setView([48.85, 2.35], initialZoom);
102
+ console.log("L.map() called successfully.");
103
+
104
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
105
+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
106
+ maxZoom: 18,
107
+ }).on('load', function () {
108
+ console.log('Base tiles loaded.');
109
+ }).addTo(map);
110
+ console.log("Tile layer added successfully.");
111
+
112
+ if (viewToggleButton) setupViewToggleButton();
113
+ if (clusterToggleButton) setupClusterToggleButton();
114
+ setupHeatmapControls();
115
+ fetchDataAndDraw();
116
+
117
+ } catch (error) {
118
+ console.error("ERROR during Leaflet initialization:", error);
119
+ updateStatus(`Leaflet Init Failed: ${error.message}`, true);
120
+ }
121
+ }
122
+
123
+
124
+ // --- Data Fetching & Initial Drawing ---
125
+ function fetchDataAndDraw() {
126
+ if (!dataUrl) return;
127
+ console.log("Fetching coordinates from:", dataUrl);
128
+ updateStatus("Loading ticket locations...");
129
+ if (viewToggleButton) viewToggleButton.disabled = true;
130
+ if (clusterToggleButton) clusterToggleButton.disabled = true;
131
+ disableHeatmapControls(true);
132
+
133
+ fetch(dataUrl)
134
+ .then(response => {
135
+ if (!response.ok) throw new Error(`HTTP error! Status: ${response.status} ${response.statusText}`);
136
+ return response.json();
137
+ })
138
+ .then(data => {
139
+ if (data.error) throw new Error(`API Error: ${data.error}`);
140
+ if (!data || !data.locations || !Array.isArray(data.locations)) {
141
+ console.warn("Invalid data format:", data);
142
+ updateStatus("No valid locations found.", false);
143
+ coordinateData = [];
144
+ hideStatus();
145
+ return;
146
+ }
147
+ if (data.locations.length === 0) {
148
+ console.log("No locations received.");
149
+ updateStatus("No locations found for event.", false);
150
+ coordinateData = [];
151
+ hideStatus();
152
+ return;
153
+ }
154
+
155
+ coordinateData = data.locations;
156
+ console.log(`Received ${coordinateData.length} coordinates.`);
157
+
158
+ if (viewToggleButton) viewToggleButton.disabled = false;
159
+ if (clusterToggleButton) clusterToggleButton.disabled = (currentView !== 'pins');
160
+ disableHeatmapControls(false);
161
+
162
+ createAllLayers();
163
+ showCurrentView();
164
+ adjustMapBounds();
165
+ hideStatus();
166
+
167
+ setTimeout(function () {
168
+ console.log("Forcing map.invalidateSize() after data load...");
169
+ const container = document.getElementById(mapContainerId);
170
+ if (map && container && container.offsetWidth > 0) {
171
+ map.invalidateSize();
172
+ } else {
173
+ console.warn(`Skipping invalidateSize. Map: ${!!map}, Container: ${!!container}, OffsetWidth: ${container ? container.offsetWidth : 'N/A'}`);
174
+ }
175
+ }, 100);
176
+ })
177
+ .catch(error => {
178
+ console.error('Error fetching/processing data:', error);
179
+ updateStatus(`Error loading map data: ${error.message}.`, true);
180
+ });
181
+ }
182
+
183
+
184
+ // --- Layer Creation Functions ---
185
+ function createAllLayers() {
186
+ createPinLayer();
187
+ createHeatmapLayer();
188
+ console.log("Layers created/updated.");
189
+ }
190
+
191
+ function createPinLayer() {
192
+ console.log(`Creating pin layer (Clustering: ${isClusteringEnabled})...`);
193
+ pinLayer = null;
194
+ if (coordinateData.length === 0) {
195
+ console.warn("No data for pin layer.");
196
+ return;
197
+ }
198
+ const markers = [];
199
+ coordinateData.forEach((loc, index) => {
200
+ try {
201
+ if (loc.lat == null || loc.lon == null || isNaN(loc.lat) || isNaN(loc.lon)) return;
202
+ const marker = L.marker(L.latLng(loc.lat, loc.lon));
203
+ if (loc.tooltip) marker.bindTooltip(loc.tooltip);
204
+ if (loc.order_url) {
205
+ marker.on('click', () => window.open(loc.order_url, '_blank'));
206
+ }
207
+ markers.push(marker);
208
+ } catch (e) {
209
+ console.error(`Error creating marker ${index}:`, e);
210
+ }
211
+ });
212
+ if (markers.length === 0) {
213
+ console.warn("No valid markers created.");
214
+ return;
215
+ }
216
+ if (isClusteringEnabled) {
217
+ pinLayer = L.markerClusterGroup();
218
+ pinLayer.addLayers(markers);
219
+ console.log("Marker cluster populated.");
220
+ } else {
221
+ pinLayer = L.layerGroup(markers);
222
+ console.log("Simple layer group populated.");
223
+ }
224
+ }
225
+
226
+ function createHeatmapLayer() {
227
+ console.log("Creating heatmap layer...");
228
+ heatmapLayer = null;
229
+ if (coordinateData.length === 0) {
230
+ console.warn("No data for heatmap.");
231
+ return;
232
+ }
233
+ try {
234
+ const heatPoints = coordinateData.map(loc => {
235
+ if (loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon)) {
236
+ return [loc.lat, loc.lon, 1.0];
237
+ }
238
+ return null;
239
+ }).filter(p => p !== null);
240
+ if (heatPoints.length > 0) {
241
+ heatmapLayer = L.heatLayer(heatPoints, heatmapOptions);
242
+ console.log("Heatmap created:", heatmapOptions);
243
+ } else {
244
+ console.warn("No valid points for heatmap.");
245
+ }
246
+ } catch (e) {
247
+ console.error("Error creating heatmap:", e);
248
+ }
249
+ }
250
+
251
+
252
+ // --- Layer Update Functions ---
253
+ function redrawPinLayer() {
254
+ if (!map) return;
255
+ console.log("Redrawing pin layer...");
256
+ if (pinLayer && map.hasLayer(pinLayer)) map.removeLayer(pinLayer);
257
+ pinLayer = null;
258
+ createPinLayer();
259
+ if (currentView === 'pins' && pinLayer) {
260
+ console.log("Adding new pin layer.");
261
+ map.addLayer(pinLayer);
262
+ }
263
+ }
264
+
265
+ function updateHeatmap() {
266
+ if (!map || !heatmapLayer) {
267
+ console.warn("Cannot update heatmap.");
268
+ return;
269
+ }
270
+ console.log("Updating heatmap opts:", heatmapOptions);
271
+ try {
272
+ heatmapLayer.setOptions(heatmapOptions);
273
+ console.log("Heatmap opts updated.");
274
+ } catch (e) {
275
+ console.error("Error setting heatmap opts:", e);
276
+ }
277
+ }
278
+
279
+
280
+ // --- Adjust Map Bounds (Corrected) ---
281
+ function adjustMapBounds() {
282
+ if (!map || coordinateData.length === 0) return;
283
+ try {
284
+ let bounds = null;
285
+ if (currentView === 'pins' && pinLayer && typeof pinLayer.getBounds === 'function') {
286
+ bounds = pinLayer.getBounds();
287
+ console.log("Attempting bounds from pin layer.");
288
+ }
289
+ // Calculate from raw if pin layer bounds unavailable/invalid or if in heatmap view
290
+ if (!bounds || !bounds.isValid()) {
291
+ console.log("Calculating bounds from raw coordinates.");
292
+ // Filter valid lat/lon pairs
293
+ const latLngs = coordinateData
294
+ .map(loc => {
295
+ if (loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon)) {
296
+ return [loc.lat, loc.lon];
297
+ }
298
+ return null;
299
+ })
300
+ .filter(p => p !== null);
301
+ if (latLngs.length > 0) bounds = L.latLngBounds(latLngs);
302
+ }
303
+
304
+ // Apply bounds if valid
305
+ if (bounds && bounds.isValid()) {
306
+ console.log("Fitting map to bounds...");
307
+ map.fitBounds(bounds, {padding: [50, 50]});
308
+ console.log("Bounds fitted.");
309
+ // Handle single point case
310
+ } else if (coordinateData.filter(loc => loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon)).length === 1) {
311
+ console.log("Setting view for single coordinate.");
312
+ // --- Corrected logic to find the single valid coordinate ---
313
+ const singleCoord = coordinateData.find(loc => loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon));
314
+ // --- End Correction ---
315
+ if (singleCoord) map.setView([singleCoord.lat, singleCoord.lon], 13);
316
+ else console.warn("Could not find single valid coordinate to set view.");
317
+ // Removed extra ')' here
318
+ } else {
319
+ console.warn("Could not determine valid bounds.");
320
+ }
321
+ } catch (e) {
322
+ console.error("Error fitting map bounds:", e);
323
+ }
324
+ } // End adjustMapBounds function
325
+
326
+
327
+ // --- Control Setup Functions ---
328
+ function setupViewToggleButton() {
329
+ updateViewToggleButtonText();
330
+ viewToggleButton.addEventListener('click', () => {
331
+ console.log("View toggle clicked!");
332
+ currentView = (currentView === 'pins') ? 'heatmap' : 'pins';
333
+ showCurrentView();
334
+ updateViewToggleButtonText();
335
+ if (clusterToggleButton) clusterToggleButton.disabled = (currentView !== 'pins');
336
+ });
337
+ console.log("View toggle listener setup.");
338
+ }
339
+
340
+ function setupClusterToggleButton() {
341
+ updateClusterToggleButtonText();
342
+ clusterToggleButton.disabled = (currentView !== 'pins');
343
+ clusterToggleButton.addEventListener('click', () => {
344
+ if (currentView !== 'pins') return;
345
+ console.log("Cluster toggle clicked!");
346
+ isClusteringEnabled = !isClusteringEnabled;
347
+ redrawPinLayer();
348
+ updateClusterToggleButtonText();
349
+ });
350
+ console.log("Cluster toggle listener setup.");
351
+ }
352
+
353
+ function setupHeatmapControls() {
354
+ if (!heatmapRadiusInput || !heatmapBlurInput || !heatmapMaxZoomInput || !radiusValueSpan || !blurValueSpan || !maxZoomValueSpan) {
355
+ console.error("Heatmap controls missing.");
356
+ return;
357
+ }
358
+ radiusValueSpan.textContent = heatmapOptions.radius;
359
+ heatmapRadiusInput.value = heatmapOptions.radius;
360
+ blurValueSpan.textContent = heatmapOptions.blur;
361
+ heatmapBlurInput.value = heatmapOptions.blur;
362
+ maxZoomValueSpan.textContent = heatmapOptions.maxZoom;
363
+ heatmapMaxZoomInput.value = heatmapOptions.maxZoom;
364
+ heatmapRadiusInput.addEventListener('input', (e) => {
365
+ const v = parseFloat(e.target.value);
366
+ heatmapOptions.radius = v;
367
+ radiusValueSpan.textContent = v;
368
+ updateHeatmap();
369
+ });
370
+ heatmapBlurInput.addEventListener('input', (e) => {
371
+ const v = parseFloat(e.target.value);
372
+ heatmapOptions.blur = v;
373
+ blurValueSpan.textContent = v;
374
+ updateHeatmap();
375
+ });
376
+ heatmapMaxZoomInput.addEventListener('input', (e) => {
377
+ const v = parseInt(e.target.value, 10);
378
+ heatmapOptions.maxZoom = v;
379
+ maxZoomValueSpan.textContent = v;
380
+ updateHeatmap();
381
+ });
382
+ console.log("Heatmap control listeners setup.");
383
+ }
384
+
385
+ function disableHeatmapControls(disabled) {
386
+ if (heatmapRadiusInput) heatmapRadiusInput.disabled = disabled;
387
+ if (heatmapBlurInput) heatmapBlurInput.disabled = disabled;
388
+ if (heatmapMaxZoomInput) heatmapMaxZoomInput.disabled = disabled;
389
+ }
390
+
391
+
392
+ // --- View Switching Logic ---
393
+ function showCurrentView() {
394
+ console.log(`Showing view: ${currentView}`);
395
+ if (!map) {
396
+ console.warn("Map not init.");
397
+ return;
398
+ }
399
+ console.log("Removing layers...");
400
+ if (pinLayer && map.hasLayer(pinLayer)) map.removeLayer(pinLayer);
401
+ if (heatmapLayer && map.hasLayer(heatmapLayer)) map.removeLayer(heatmapLayer);
402
+ console.log(`Adding ${currentView} layer...`);
403
+ let layerToAdd = null;
404
+ if (currentView === 'pins') {
405
+ layerToAdd = pinLayer;
406
+ if (heatmapOptionsPanel) heatmapOptionsPanel.style.display = 'none';
407
+ if (clusterToggleButton) {
408
+ clusterToggleButton.style.display = 'inline-block';
409
+ clusterToggleButton.disabled = false;
410
+ }
411
+ } else {
412
+ layerToAdd = heatmapLayer;
413
+ if (heatmapOptionsPanel) heatmapOptionsPanel.style.display = 'block';
414
+ if (clusterToggleButton) {
415
+ clusterToggleButton.style.display = 'none';
416
+ clusterToggleButton.disabled = true;
417
+ }
418
+ }
419
+ if (layerToAdd) {
420
+ try {
421
+ map.addLayer(layerToAdd);
422
+ console.log(`Added ${currentView} layer.`);
423
+ } catch (e) {
424
+ console.error(`Error adding ${currentView}:`, e);
425
+ updateStatus(`Error display ${currentView}.`, true);
426
+ }
427
+ } else {
428
+ console.warn(`Layer instance missing for ${currentView}.`);
429
+ updateStatus(`No data for ${currentView}.`, false);
430
+ }
431
+ }
432
+
433
+
434
+ // --- Button Text Update Functions ---
435
+ function updateViewToggleButtonText() {
436
+ if (!viewToggleButton) return;
437
+ const next = (currentView === 'pins') ? 'Heatmap' : 'Pin';
438
+ viewToggleButton.textContent = `Switch to ${next} View`;
439
+ console.log(`View Btn text: ${viewToggleButton.textContent}`);
440
+ }
441
+
442
+ function updateClusterToggleButtonText() {
443
+ if (!clusterToggleButton) return;
444
+ clusterToggleButton.textContent = isClusteringEnabled ? 'Disable Clustering' : 'Enable Clustering';
445
+ console.log(`Cluster Btn text: ${clusterToggleButton.textContent}`);
446
+ }
447
+
448
+
449
+ // --- Start Initialization ---
450
+ initializeMap();
451
+
452
+ }); // End DOMContentLoaded
@@ -0,0 +1,88 @@
1
+ {% extends "pretixcontrol/event/settings_base.html" %}
2
+ {% load i18n %}
3
+ {% load static %}
4
+ {% load eventurl %}
5
+
6
+ {% block title %}{% trans "Ticket Sales Map" %}{% endblock %}
7
+
8
+ {% block inside %}
9
+
10
+ <div class="plugin-map-content-wrapper">
11
+
12
+ <h1>{% trans "Ticket Sales Map" %}</h1>
13
+
14
+ <div class="form-inline map-controls-row"
15
+ style="margin-bottom: 1em; display: flex; flex-wrap: wrap; align-items: flex-start; gap: 15px;">
16
+
17
+ <div class="map-buttons-group" style="display: flex; flex-wrap: wrap; gap: 10px; align-items: center;">
18
+ <div class="form-group">
19
+ <button id="view-toggle-btn" class="btn btn-default" disabled>Switch to Heatmap View</button>
20
+ </div>
21
+ <div class="form-group">
22
+ <button id="cluster-toggle-btn" class="btn btn-default" disabled style="display: inline-block;">
23
+ Disable Clustering
24
+ </button>
25
+ </div>
26
+ </div>
27
+ <div id="heatmap-options-panel" class="panel panel-default"
28
+ style="display: none; padding: 10px 15px; border-radius: 4px; min-width: 350px;">
29
+ <h5 style="margin-top: 0; margin-bottom: 10px;">{% trans "Heatmap Options" %}</h5>
30
+ <div class="form-horizontal">
31
+ <div class="form-group form-group-sm" style="margin-bottom: 5px;">
32
+ <label for="heatmap-radius" class="col-sm-3 control-label"
33
+ style="padding-top: 5px;">Radius</label>
34
+ <div class="col-sm-7">
35
+ <input type="range" id="heatmap-radius" class="form-control" min="1" max="100" value="25"
36
+ step="1" disabled>
37
+ </div>
38
+ <div class="col-sm-2">
39
+ <span id="radius-value" class="form-control-static">25</span>
40
+ </div>
41
+ </div>
42
+ <div class="form-group form-group-sm" style="margin-bottom: 5px;">
43
+ <label for="heatmap-blur" class="col-sm-3 control-label" style="padding-top: 5px;">Blur</label>
44
+ <div class="col-sm-7">
45
+ <input type="range" id="heatmap-blur" class="form-control" min="1" max="50" value="15"
46
+ step="1" disabled>
47
+ </div>
48
+ <div class="col-sm-2">
49
+ <span id="blur-value" class="form-control-static">15</span>
50
+ </div>
51
+ </div>
52
+ <div class="form-group form-group-sm" style="margin-bottom: 5px;">
53
+ <label for="heatmap-maxZoom" class="col-sm-3 control-label" style="padding-top: 5px;">Max
54
+ Zoom</label>
55
+ <div class="col-sm-7">
56
+ <input type="range" id="heatmap-maxZoom" class="form-control" min="1" max="18" value="18"
57
+ step="1" disabled>
58
+ </div>
59
+ <div class="col-sm-2">
60
+ <span id="maxzoom-value" class="form-control-static">18</span>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <div class="map-wrapper" style="position: relative; border: 1px solid #ccc; flex-grow: 1; min-height: 0;">
68
+ <div id="sales-map-container"
69
+ data-data-url="{% url 'plugins:pretix_mapplugin:event.settings.salesmap.data' organizer=request.organizer.slug event=request.event.slug %}">
70
+ </div>
71
+ <div id="map-status-overlay"
72
+ style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); z-index: 1000; display: flex; justify-content: center; align-items: center; text-align: center;">
73
+ <p>Loading map data...</p>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <script src="
79
+ {% static 'pretix_mapplugin/libs/leaflet-sales-map/leaflet-heat.js' %}"></script>
80
+ <link rel="stylesheet" href="{% static 'pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.css' %}"/>
81
+ <link rel="stylesheet"
82
+ href="{% static 'pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.Default.css' %}"/>
83
+ <script src="{% static 'pretix_mapplugin/libs/leaflet-sales-map/leaflet.markercluster.js' %}"></script>
84
+
85
+ <script src="{% static 'pretix_mapplugin/js/salesmap.js' %}"></script>
86
+ <link rel="stylesheet" href="{% static 'pretix_mapplugin/css/salesmap.css' %}"/>
87
+
88
+ {% endblock %}
@@ -1 +0,0 @@
1
- __version__ = "0.1.3"
@@ -1,442 +0,0 @@
1
- document.addEventListener('DOMContentLoaded', function () {
2
- console.log("Sales Map JS Loaded (Cluster Toggle & Heatmap Opts)");
3
-
4
- // --- L.Icon.Default setup ---
5
- // Explicitly set the path to Leaflet's default icon images
6
- try {
7
- delete L.Icon.Default.prototype._getIconUrl;
8
- L.Icon.Default.mergeOptions({
9
- iconRetinaUrl: '/static/leaflet/images/marker-icon-2x.png', // Path within your collected static files
10
- iconUrl: '/static/leaflet/images/marker-icon.png', // Path within your collected static files
11
- shadowUrl: '/static/leaflet/images/marker-shadow.png', // Path within your collected static files
12
- iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34],
13
- tooltipAnchor: [16, -28], shadowSize: [41, 41]
14
- });
15
- console.log("Set Leaflet default icon image paths explicitly.");
16
- } catch (e) {
17
- console.error("Error setting Leaflet default icon path:", e);
18
- // Fallback might be needed for very old Leaflet versions
19
- // L.Icon.Default.imagePath = '/static/leaflet/images/';
20
- }
21
- // --- End L.Icon.Default setup ---
22
-
23
-
24
- // --- Configuration ---
25
- const mapContainerId = 'sales-map-container';
26
- const statusOverlayId = 'map-status-overlay';
27
- const viewToggleButtonId = 'view-toggle-btn'; // Changed from toggleButtonId for clarity
28
- const clusterToggleButtonId = 'cluster-toggle-btn';
29
- const heatmapOptionsPanelId = 'heatmap-options-panel';
30
- const initialZoom = 5; // Start more zoomed out
31
- const defaultMapView = 'pins'; // Start with pins
32
-
33
- // --- Globals & State ---
34
- let map = null;
35
- let coordinateData = []; // Stores {lat, lon, tooltip, order_url}
36
- let pinLayer = null; // Holds L.markerClusterGroup OR L.layerGroup
37
- let heatmapLayer = null;
38
- let currentView = defaultMapView;
39
- let dataUrl = null;
40
- let isClusteringEnabled = true; // Default Cluster State: ON
41
- let heatmapOptions = { // Default Heatmap Options (state variable)
42
- radius: 25,
43
- blur: 15,
44
- maxZoom: 18,
45
- minOpacity: 0.2 // Example optional parameter
46
- };
47
-
48
- // --- DOM Elements ---
49
- const mapElement = document.getElementById(mapContainerId);
50
- const statusOverlay = document.getElementById(statusOverlayId);
51
- const viewToggleButton = document.getElementById(viewToggleButtonId);
52
- const clusterToggleButton = document.getElementById(clusterToggleButtonId);
53
- const heatmapOptionsPanel = document.getElementById(heatmapOptionsPanelId);
54
- // Heatmap control elements
55
- const heatmapRadiusInput = document.getElementById('heatmap-radius');
56
- const heatmapBlurInput = document.getElementById('heatmap-blur');
57
- const heatmapMaxZoomInput = document.getElementById('heatmap-maxZoom');
58
- const radiusValueSpan = document.getElementById('radius-value');
59
- const blurValueSpan = document.getElementById('blur-value');
60
- const maxZoomValueSpan = document.getElementById('maxzoom-value');
61
-
62
- // --- Status Update Helpers ---
63
- function updateStatus(message, isError = false) {
64
- if (statusOverlay) {
65
- const p = statusOverlay.querySelector('p');
66
- if (p) { p.textContent = message; p.className = isError ? 'text-danger' : ''; }
67
- statusOverlay.style.display = 'flex'; // Show overlay when status updates
68
- } else { console.warn("Status overlay element not found."); }
69
- }
70
- function hideStatus() {
71
- if (statusOverlay) { statusOverlay.style.display = 'none'; } // Hide overlay
72
- }
73
- // --- End Status Helpers ---
74
-
75
-
76
- // --- Initialization ---
77
- function initializeMap() {
78
- // Check required elements
79
- if (!mapElement) { console.error(`Map container #${mapContainerId} not found.`); return; }
80
- if (!statusOverlay) { console.warn("Status overlay element not found."); }
81
- // Check control elements (warn but continue if missing)
82
- if (!viewToggleButton) console.warn("View toggle button not found.");
83
- if (!clusterToggleButton) console.warn("Cluster toggle button not found.");
84
- if (!heatmapOptionsPanel) console.warn("Heatmap options panel not found.");
85
- if (!heatmapRadiusInput || !heatmapBlurInput || !heatmapMaxZoomInput) console.warn("Heatmap input elements missing.");
86
-
87
- // Get Data URL
88
- dataUrl = mapElement.dataset.dataUrl;
89
- if (!dataUrl) {
90
- updateStatus("Configuration Error: Missing data source URL.", true);
91
- return;
92
- }
93
- console.log(`Data URL found: ${dataUrl}`);
94
- updateStatus("Initializing map...");
95
-
96
- // Create Leaflet Map
97
- try {
98
- map = L.map(mapContainerId).setView([48.85, 2.35], initialZoom); // Centered somewhat on Europe
99
- console.log("L.map() called successfully.");
100
-
101
- // Add Base Tile Layer
102
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
103
- attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
104
- maxZoom: 18, // Standard max zoom
105
- }).on('load', function() { console.log('Base tiles loaded.'); }).addTo(map);
106
- console.log("Tile layer added successfully.");
107
-
108
- // Setup Controls (Event Listeners)
109
- if (viewToggleButton) setupViewToggleButton();
110
- if (clusterToggleButton) setupClusterToggleButton();
111
- setupHeatmapControls(); // Setup listeners even if panel hidden
112
-
113
- // Fetch data to populate layers
114
- fetchDataAndDraw();
115
-
116
- } catch (error) {
117
- console.error("ERROR during Leaflet initialization:", error);
118
- updateStatus(`Leaflet Init Failed: ${error.message}`, true);
119
- }
120
- }
121
-
122
-
123
- // --- Data Fetching & Initial Drawing ---
124
- function fetchDataAndDraw() {
125
- if (!dataUrl) return;
126
- console.log("Fetching coordinates from:", dataUrl);
127
- updateStatus("Loading ticket locations...");
128
- // Disable controls while loading
129
- if(viewToggleButton) viewToggleButton.disabled = true;
130
- if(clusterToggleButton) clusterToggleButton.disabled = true;
131
- disableHeatmapControls(true);
132
-
133
- fetch(dataUrl)
134
- .then(response => {
135
- if (!response.ok) throw new Error(`HTTP error! Status: ${response.status} ${response.statusText}`);
136
- return response.json();
137
- })
138
- .then(data => {
139
- if (data.error) throw new Error(`API Error: ${data.error}`);
140
- if (!data || !data.locations || !Array.isArray(data.locations)) {
141
- console.warn("Invalid or empty data format received:", data);
142
- updateStatus("No valid geocoded ticket locations found.", false);
143
- coordinateData = []; hideStatus(); return;
144
- }
145
- if (data.locations.length === 0) {
146
- console.log("No coordinate data received (empty list).");
147
- updateStatus("No geocoded ticket locations found for this event.", false);
148
- coordinateData = []; hideStatus(); return;
149
- }
150
-
151
- coordinateData = data.locations;
152
- console.log(`Received ${coordinateData.length} coordinates.`);
153
-
154
- // Enable controls now that we have data
155
- if(viewToggleButton) viewToggleButton.disabled = false;
156
- // Only enable cluster button if in pins view initially and data exists
157
- if(clusterToggleButton) clusterToggleButton.disabled = (currentView !== 'pins');
158
- // Only enable heatmap controls if data exists
159
- disableHeatmapControls(false);
160
-
161
- // Create both layers based on fetched data
162
- createAllLayers();
163
- // Display the default layer
164
- showCurrentView();
165
- // Adjust bounds to fit the data
166
- adjustMapBounds();
167
-
168
- hideStatus(); // Hide loading overlay
169
-
170
- // Force redraw after a short delay
171
- setTimeout(function () {
172
- console.log("Forcing map.invalidateSize() after data load...");
173
- const container = document.getElementById(mapContainerId);
174
- if (map && container && container.offsetWidth > 0) {
175
- map.invalidateSize();
176
- } else {
177
- console.warn(`Skipping invalidateSize. Map: ${!!map}, Container: ${!!container}, OffsetWidth: ${container ? container.offsetWidth : 'N/A'}`);
178
- }
179
- }, 100);
180
- })
181
- .catch(error => {
182
- console.error('Error fetching or processing coordinate data:', error);
183
- updateStatus(`Error loading map data: ${error.message}. Please try again later.`, true);
184
- // Keep controls disabled on error
185
- });
186
- }
187
-
188
-
189
- // --- Layer Creation Functions ---
190
- function createAllLayers() {
191
- // Orchestrates creation of both layer types
192
- createPinLayer();
193
- createHeatmapLayer();
194
- console.log("Map layer instances created/updated.");
195
- }
196
-
197
- function createPinLayer() {
198
- // Creates/recreates the pin layer based on isClusteringEnabled state
199
- console.log(`Creating pin layer instance (Clustering: ${isClusteringEnabled})...`);
200
- pinLayer = null; // Clear previous instance
201
- if (coordinateData.length === 0) { console.warn("No coordinate data for pin layer."); return; }
202
-
203
- const markers = [];
204
- coordinateData.forEach((loc, index) => {
205
- try {
206
- if (loc.lat == null || loc.lon == null || isNaN(loc.lat) || isNaN(loc.lon)) return;
207
- const latLng = L.latLng(loc.lat, loc.lon);
208
- const marker = L.marker(latLng);
209
- if (loc.tooltip) marker.bindTooltip(loc.tooltip);
210
- if (loc.order_url) { marker.on('click', () => window.open(loc.order_url, '_blank')); }
211
- markers.push(marker);
212
- } catch (e) { console.error(`Error creating marker ${index}:`, e); }
213
- });
214
-
215
- if (markers.length === 0) { console.warn("No valid markers created for pin layer."); return; }
216
-
217
- if (isClusteringEnabled) {
218
- pinLayer = L.markerClusterGroup(); // Use clustering
219
- pinLayer.addLayers(markers);
220
- console.log("Marker cluster group created and populated.");
221
- } else {
222
- pinLayer = L.layerGroup(markers); // Use simple layer group
223
- console.log("Simple layer group created and populated.");
224
- }
225
- }
226
-
227
- function createHeatmapLayer() {
228
- // Creates/recreates the heatmap layer using current heatmapOptions
229
- console.log("Creating heatmap layer instance...");
230
- heatmapLayer = null; // Clear previous instance
231
- if (coordinateData.length === 0) { console.warn("No coordinate data for heatmap layer."); return; }
232
-
233
- try {
234
- const heatPoints = coordinateData.map(loc => {
235
- if (loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon)) {
236
- return [loc.lat, loc.lon, 1.0]; // Intensity 1.0
237
- } return null;
238
- }).filter(p => p !== null);
239
-
240
- if (heatPoints.length > 0) {
241
- heatmapLayer = L.heatLayer(heatPoints, heatmapOptions); // Use state variable
242
- console.log("Heatmap layer instance created with options:", heatmapOptions);
243
- } else { console.warn("No valid points for heatmap layer."); }
244
- } catch (e) { console.error("Error creating heatmap layer instance:", e); }
245
- }
246
-
247
-
248
- // --- Layer Update Functions ---
249
- function redrawPinLayer() {
250
- // Removes existing pin layer, calls createPinLayer, adds new one if current view is 'pins'
251
- if (!map) return;
252
- console.log("Redrawing pin layer...");
253
- if (pinLayer && map.hasLayer(pinLayer)) {
254
- map.removeLayer(pinLayer);
255
- console.log("Removed existing pin layer before redraw.");
256
- }
257
- pinLayer = null; // Ensure it's cleared
258
- createPinLayer(); // Recreate based on current clustering state
259
- if (currentView === 'pins' && pinLayer) {
260
- console.log("Adding newly created pin layer to map.");
261
- map.addLayer(pinLayer);
262
- }
263
- }
264
-
265
- function updateHeatmap() {
266
- // Updates options on the existing heatmap layer if it's present
267
- if (!map || !heatmapLayer) {
268
- console.warn("Cannot update heatmap: map or heatmap layer missing.");
269
- return;
270
- }
271
- console.log("Updating heatmap options:", heatmapOptions);
272
- try {
273
- // Use Leaflet.heat's setOptions method
274
- heatmapLayer.setOptions(heatmapOptions);
275
- console.log("Heatmap options updated.");
276
- } catch(e) {
277
- console.error("Error setting heatmap options:", e);
278
- }
279
- }
280
-
281
-
282
- // --- Adjust Map Bounds ---
283
- function adjustMapBounds() {
284
- // (Keep the working version from previous steps)
285
- if (!map || coordinateData.length === 0) return;
286
- try {
287
- let bounds = null;
288
- if (currentView === 'pins' && pinLayer && typeof pinLayer.getBounds === 'function') {
289
- bounds = pinLayer.getBounds();
290
- console.log("Attempting bounds from pin layer.");
291
- }
292
- if (!bounds || !bounds.isValid()) {
293
- console.log("Calculating bounds from raw coordinates.");
294
- const latLngs = coordinateData.map(loc => { /* ... filter valid ... */ return [loc.lat, loc.lon]; }).filter(p => p !== null);
295
- if (latLngs.length > 0) bounds = L.latLngBounds(latLngs);
296
- }
297
- if (bounds && bounds.isValid()) {
298
- console.log("Fitting map to bounds..."); map.fitBounds(bounds, { padding: [50, 50] }); console.log("Bounds fitted.");
299
- } else if (coordinateData.length === 1) {
300
- console.log("Setting view for single coordinate.");
301
- const singleCoord = coordinateData.find(loc => /* ... valid ... */);
302
- if (singleCoord) map.setView([singleCoord.lat, singleCoord.lon], 13);
303
- else console.warn("Could not find single valid coordinate.");
304
- } else { console.warn("Could not determine valid bounds."); }
305
- } catch (e) { console.error("Error fitting map bounds:", e); }
306
- }
307
-
308
-
309
- // --- Control Setup Functions ---
310
- function setupViewToggleButton() {
311
- updateViewToggleButtonText(); // Initial text
312
- viewToggleButton.addEventListener('click', () => {
313
- console.log("View toggle button clicked!");
314
- currentView = (currentView === 'pins') ? 'heatmap' : 'pins';
315
- showCurrentView(); // Update map display
316
- updateViewToggleButtonText(); // Update button text
317
- // Enable/disable cluster button based on view
318
- if (clusterToggleButton) clusterToggleButton.disabled = (currentView !== 'pins');
319
- });
320
- console.log("View toggle button listener setup complete.");
321
- }
322
-
323
- function setupClusterToggleButton() {
324
- updateClusterToggleButtonText(); // Initial text
325
- // Start disabled if not in pins view
326
- clusterToggleButton.disabled = (currentView !== 'pins');
327
- clusterToggleButton.addEventListener('click', () => {
328
- if (currentView !== 'pins') return; // Safety check
329
- console.log("Cluster toggle button clicked!");
330
- isClusteringEnabled = !isClusteringEnabled; // Toggle state
331
- redrawPinLayer(); // Recreate and potentially re-add the pin layer
332
- updateClusterToggleButtonText(); // Update button text
333
- });
334
- console.log("Cluster toggle button listener setup complete.");
335
- }
336
-
337
- function setupHeatmapControls() {
338
- // Check if all elements exist before adding listeners
339
- if (!heatmapRadiusInput || !heatmapBlurInput || !heatmapMaxZoomInput || !radiusValueSpan || !blurValueSpan || !maxZoomValueSpan) {
340
- console.error("One or more heatmap control elements not found. Cannot setup listeners.");
341
- return;
342
- }
343
-
344
- // Set initial display values from defaults
345
- radiusValueSpan.textContent = heatmapOptions.radius;
346
- heatmapRadiusInput.value = heatmapOptions.radius;
347
- blurValueSpan.textContent = heatmapOptions.blur;
348
- heatmapBlurInput.value = heatmapOptions.blur;
349
- maxZoomValueSpan.textContent = heatmapOptions.maxZoom;
350
- heatmapMaxZoomInput.value = heatmapOptions.maxZoom;
351
-
352
- // Add 'input' listeners for real-time updates
353
- heatmapRadiusInput.addEventListener('input', (e) => {
354
- const value = parseFloat(e.target.value);
355
- heatmapOptions.radius = value; // Update state
356
- radiusValueSpan.textContent = value; // Update display
357
- updateHeatmap(); // Apply change to map
358
- });
359
- heatmapBlurInput.addEventListener('input', (e) => {
360
- const value = parseFloat(e.target.value);
361
- heatmapOptions.blur = value; // Update state
362
- blurValueSpan.textContent = value; // Update display
363
- updateHeatmap(); // Apply change to map
364
- });
365
- heatmapMaxZoomInput.addEventListener('input', (e) => {
366
- const value = parseInt(e.target.value, 10);
367
- heatmapOptions.maxZoom = value; // Update state
368
- maxZoomValueSpan.textContent = value; // Update display
369
- updateHeatmap(); // Apply change to map
370
- });
371
- console.log("Heatmap control listeners setup complete.");
372
- }
373
-
374
- function disableHeatmapControls(disabled) {
375
- // Helper to enable/disable all heatmap inputs
376
- if (heatmapRadiusInput) heatmapRadiusInput.disabled = disabled;
377
- if (heatmapBlurInput) heatmapBlurInput.disabled = disabled;
378
- if (heatmapMaxZoomInput) heatmapMaxZoomInput.disabled = disabled;
379
- }
380
-
381
-
382
- // --- View Switching Logic ---
383
- function showCurrentView() {
384
- console.log(`Showing view: ${currentView}`);
385
- if (!map) { console.warn("Map not initialized."); return; }
386
-
387
- // Remove existing layers from map
388
- console.log("Removing existing layers (if present)...");
389
- if (pinLayer && map.hasLayer(pinLayer)) map.removeLayer(pinLayer);
390
- if (heatmapLayer && map.hasLayer(heatmapLayer)) map.removeLayer(heatmapLayer);
391
-
392
- // Add the selected layer and show/hide controls
393
- console.log(`Adding ${currentView} layer...`);
394
- let layerToAdd = null;
395
- if (currentView === 'pins') {
396
- layerToAdd = pinLayer;
397
- if(heatmapOptionsPanel) heatmapOptionsPanel.style.display = 'none';
398
- if(clusterToggleButton) clusterToggleButton.style.display = 'inline-block'; // Show cluster btn
399
- if(clusterToggleButton) clusterToggleButton.disabled = false; // Ensure enabled
400
- } else { // Heatmap view
401
- layerToAdd = heatmapLayer;
402
- if(heatmapOptionsPanel) heatmapOptionsPanel.style.display = 'block';
403
- if(clusterToggleButton) clusterToggleButton.style.display = 'none'; // Hide cluster btn
404
- if(clusterToggleButton) clusterToggleButton.disabled = true; // Ensure disabled
405
- }
406
-
407
- // Add the layer to the map if it exists
408
- if (layerToAdd) {
409
- try {
410
- map.addLayer(layerToAdd);
411
- console.log(`Added ${currentView} layer to map.`);
412
- } catch (e) {
413
- console.error(`Error adding ${currentView} layer:`, e);
414
- updateStatus(`Error displaying ${currentView} layer.`, true);
415
- }
416
- } else {
417
- // This happens if createLayer failed (e.g., no valid data)
418
- console.warn(`Cannot add layer for view "${currentView}": Layer instance missing.`);
419
- updateStatus(`No data available for ${currentView} view.`, false);
420
- }
421
- }
422
-
423
-
424
- // --- Button Text Update Functions ---
425
- function updateViewToggleButtonText() {
426
- if (!viewToggleButton) return;
427
- const nextViewText = (currentView === 'pins') ? 'Heatmap' : 'Pin';
428
- viewToggleButton.textContent = `Switch to ${nextViewText} View`;
429
- console.log(`View Toggle Button text updated to: ${viewToggleButton.textContent}`);
430
- }
431
-
432
- function updateClusterToggleButtonText() {
433
- if (!clusterToggleButton) return;
434
- clusterToggleButton.textContent = isClusteringEnabled ? 'Disable Clustering' : 'Enable Clustering';
435
- console.log(`Cluster Toggle Button text updated to: ${clusterToggleButton.textContent}`);
436
- }
437
-
438
-
439
- // --- Start Initialization ---
440
- initializeMap();
441
-
442
- }); // End DOMContentLoaded
@@ -1,68 +0,0 @@
1
- {% extends "pretixcontrol/event/settings_base.html" %}
2
- {% load i18n %}
3
- {% load static %}
4
- {% load eventurl %}
5
-
6
- {% block title %}{% trans "Ticket Sales Map" %}{% endblock %}
7
-
8
- {% block inside %}
9
- <h1>{% trans "Ticket Sales Map" %}</h1>
10
-
11
- {# -- Controls Row -- #}
12
- <div class="form-inline" style="margin-bottom: 1em;">
13
- <div class="form-group" style="margin-right: 10px;">
14
- <button id="view-toggle-btn" class="btn btn-default">Switch to Heatmap View</button>
15
- </div>
16
- {# -- NEW Cluster Toggle Button -- #}
17
- <div class="form-group" style="margin-right: 10px;">
18
- <button id="cluster-toggle-btn" class="btn btn-default" disabled>Disable Clustering</button>
19
- </div>
20
- {# -- End Cluster Toggle Button -- #}
21
- </div>
22
- {# -- End Controls Row -- #}
23
-
24
- {# -- NEW Heatmap Options Panel (Initially hidden for pins view) -- #}
25
- <div id="heatmap-options-panel" class="panel panel-default"
26
- style="display: none; padding: 15px; margin-bottom: 1em; max-width: 400px;">
27
- <h4>{% trans "Heatmap Options" %}</h4>
28
- <div class="form-group">
29
- <label for="heatmap-radius">Radius: <span id="radius-value">25</span></label>
30
- <input type="range" id="heatmap-radius" class="form-control" min="1" max="100" value="25" step="1" disabled>
31
- </div>
32
- <div class="form-group">
33
- <label for="heatmap-blur">Blur: <span id="blur-value">15</span></label>
34
- <input type="range" id="heatmap-blur" class="form-control" min="1" max="50" value="15" step="1" disabled>
35
- </div>
36
- <div class="form-group">
37
- <label for="heatmap-maxZoom">Max Zoom: <span id="maxzoom-value">18</span></label>
38
- <input type="range" id="heatmap-maxZoom" class="form-control" min="1" max="18" value="18" step="1" disabled>
39
- </div>
40
- {# Add more options like gradient if desired later #}
41
- </div>
42
- {# -- End Heatmap Options Panel -- #}
43
-
44
-
45
- {# -- Map Area -- #}
46
- <div style="position: relative; border: 1px solid #ccc;">
47
- <div id="sales-map-container"
48
- data-data-url="{% url 'plugins:pretix_mapplugin:event.settings.salesmap.data' organizer=request.organizer.slug event=request.event.slug %}">
49
- </div>
50
- <div id="map-status-overlay"
51
- style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); z-index: 1000; display: flex; justify-content: center; align-items: center; text-align: center;">
52
- <p>Loading map data...</p>
53
- </div>
54
- </div>
55
- {# -- End Map Area -- #}
56
-
57
-
58
- {# === Leaflet & Plugins (Keep as is) === #}
59
- <script src="{% static 'pretix_mapplugin/libs/leaflet-sales-map/leaflet-heat.js' %}"></script>
60
- <link rel="stylesheet" href="{% static 'pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.css' %}"/>
61
- <link rel="stylesheet" href="{% static 'pretix_mapplugin/libs/leaflet-sales-map/MarkerCluster.Default.css' %}"/>
62
- <script src="{% static 'pretix_mapplugin/libs/leaflet-sales-map/leaflet.markercluster.js' %}"></script>
63
-
64
- {# === Your JS/CSS (Keep as is) === #}
65
- <script src="{% static 'pretix_mapplugin/js/salesmap.js' %}"></script>
66
- <link rel="stylesheet" href="{% static 'pretix_mapplugin/css/salesmap.css' %}"/>
67
-
68
- {% endblock %}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes