CSV Import is now working
This commit is contained in:
@@ -10,233 +10,365 @@
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://www.gstatic.com/charts/loader.js"
|
||||
></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/ol@7.5.2/ol.css"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/ol@7.5.2/dist/ol.js"></script>
|
||||
|
||||
<style>
|
||||
/* CRITICAL: Prevent any duplicate maps */
|
||||
.ol-viewport {
|
||||
max-width: 100% !important;
|
||||
max-height: 700px !important;
|
||||
}
|
||||
|
||||
#single-map {
|
||||
width: 100%;
|
||||
height: 700px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.map-controls {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.ol-popup {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
bottom: 12px;
|
||||
left: -50px;
|
||||
min-width: 200px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.ol-popup:after {
|
||||
top: 100%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border-color: rgba(255, 255, 255, 0);
|
||||
border-top-color: #ffffff;
|
||||
border-width: 10px;
|
||||
left: 48px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
<!-- Full Width Container -->
|
||||
<div class="min-h-screen w-full flex flex-col">
|
||||
<!-- Main Dashboard Content -->
|
||||
<div class="w-full">
|
||||
<!-- Full Width Container -->
|
||||
<div class="min-h-screen w-full flex flex-col">
|
||||
<!-- Top Navigation Bar -->
|
||||
<div class="bg-white border-b border-gray-200 w-full">
|
||||
<div class="px-8 py-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-blue-600 flex items-center justify-center">
|
||||
<i class="fas fa-chart-bar text-white text-sm"></i>
|
||||
</div>
|
||||
<span class="text-xl font-semibold text-gray-900">
|
||||
Dashboard Overview
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<button
|
||||
class="px-6 py-2.5 bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
<i class="fas fa-download mr-2"></i>Export Data
|
||||
</button>
|
||||
<button
|
||||
class="px-6 py-2.5 bg-green-600 text-white text-sm font-medium hover:bg-green-700 transition-colors"
|
||||
onclick="window.location.href='/addresses/upload-csv'"
|
||||
>
|
||||
<i class="fas fa-upload mr-2"></i>Import Data
|
||||
</button>
|
||||
<!-- <button
|
||||
class="px-6 py-2.5 border border-gray-300 text-gray-700 text-sm font-medium hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<i class="fas fa-filter mr-2"></i>Filter
|
||||
</button> -->
|
||||
<!-- Navigation -->
|
||||
<div class="bg-white border-b border-gray-200 w-full">
|
||||
<div class="px-8 py-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-blue-600 flex items-center justify-center">
|
||||
<i class="fas fa-chart-bar text-white text-sm"></i>
|
||||
</div>
|
||||
<span class="text-xl font-semibold text-gray-900"
|
||||
>Dashboard Overview</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Stats Grid - Full Width -->
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 bg-white border-b border-gray-200"
|
||||
>
|
||||
<!-- Active Volunteers -->
|
||||
<div
|
||||
class="border-r border-gray-200 p-8 hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
onclick="focusMap()"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="w-12 h-12 bg-blue-50 flex items-center justify-center"
|
||||
>
|
||||
<i class="fas fa-users text-blue-600 text-lg"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">
|
||||
Active Volunteers
|
||||
</p>
|
||||
<p class="text-2xl font-bold text-gray-900">
|
||||
{{.VolunteerCount}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Addresses Visited -->
|
||||
<div
|
||||
class="border-r border-gray-200 p-8 hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
onclick="updateChart('visitors')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="w-12 h-12 bg-blue-50 flex items-center justify-center"
|
||||
>
|
||||
<i class="fas fa-map-marker-alt text-blue-600 text-lg"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">
|
||||
Addresses Visited
|
||||
</p>
|
||||
<p class="text-2xl font-bold text-gray-900">
|
||||
{{.ValidatedCount}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total Donations -->
|
||||
<div
|
||||
class="border-r border-gray-200 p-8 hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
onclick="updateChart('revenue')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="w-12 h-12 bg-blue-50 flex items-center justify-center"
|
||||
>
|
||||
<i class="fas fa-dollar-sign text-blue-600 text-lg"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">Donation</p>
|
||||
<p class="text-2xl font-bold text-gray-900">
|
||||
${{.TotalDonations}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Houses Left -->
|
||||
<div
|
||||
class="p-8 hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
onclick="updateChart('conversion')"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="w-12 h-12 bg-blue-50 flex items-center justify-center"
|
||||
>
|
||||
<i class="fas fa-percentage text-blue-600 text-lg"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">
|
||||
Houses Left
|
||||
</p>
|
||||
<p class="text-2xl font-bold text-gray-900">
|
||||
{{.HousesLeftPercent}}%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Section - Full Width -->
|
||||
<div class="bg-white w-full">
|
||||
<div class="px-8 py-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-6">
|
||||
Location Analytics
|
||||
</h3>
|
||||
<div id="map" class="w-full h-[850px] border border-gray-200"></div>
|
||||
<div class="flex items-center gap-4">
|
||||
<button
|
||||
class="px-6 py-2.5 bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 transition-colors"
|
||||
onclick="refreshMap()"
|
||||
>
|
||||
<i class="fas fa-sync-alt mr-2"></i>Refresh Map
|
||||
</button>
|
||||
<button
|
||||
class="px-6 py-2.5 bg-green-600 text-white text-sm font-medium hover:bg-green-700 transition-colors"
|
||||
onclick="window.location.href='/addresses/upload-csv'"
|
||||
>
|
||||
<i class="fas fa-upload mr-2"></i>Import Data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Grid -->
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 bg-white border-b border-gray-200"
|
||||
>
|
||||
<div class="border-r border-gray-200 p-8">
|
||||
<div class="flex items-center">
|
||||
<div class="w-12 h-12 bg-blue-50 flex items-center justify-center">
|
||||
<i class="fas fa-users text-blue-600 text-lg"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">
|
||||
Active Volunteers
|
||||
</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{.VolunteerCount}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-r border-gray-200 p-8">
|
||||
<div class="flex items-center">
|
||||
<div class="w-12 h-12 bg-green-50 flex items-center justify-center">
|
||||
<i class="fas fa-map-marker-alt text-green-600 text-lg"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">
|
||||
Addresses Visited
|
||||
</p>
|
||||
<p class="text-2xl font-bold text-gray-900">{{.ValidatedCount}}</p>
|
||||
<p id="marker-count" class="text-xs text-gray-500">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-r border-gray-200 p-8">
|
||||
<div class="flex items-center">
|
||||
<div class="w-12 h-12 bg-yellow-50 flex items-center justify-center">
|
||||
<i class="fas fa-dollar-sign text-yellow-600 text-lg"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">Donation</p>
|
||||
<p class="text-2xl font-bold text-gray-900">${{.TotalDonations}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-8">
|
||||
<div class="flex items-center">
|
||||
<div class="w-12 h-12 bg-red-50 flex items-center justify-center">
|
||||
<i class="fas fa-percentage text-red-600 text-lg"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-medium text-gray-600 mb-1">Houses Left</p>
|
||||
<p class="text-2xl font-bold text-gray-900">
|
||||
{{.HousesLeftPercent}}%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SINGLE MAP SECTION -->
|
||||
<div class="bg-white w-full relative">
|
||||
<div class="map-controls">
|
||||
<button class="control-button" onclick="refreshMap()">
|
||||
<i class="fas fa-sync-alt"></i> Refresh
|
||||
</button>
|
||||
<button class="control-button" onclick="fitAllMarkers()">
|
||||
<i class="fas fa-expand-arrows-alt"></i> Fit All
|
||||
</button>
|
||||
<button class="control-button" onclick="clearAllMarkers()">
|
||||
<i class="fas fa-trash"></i> Clear
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- THIS IS THE ONLY MAP CONTAINER -->
|
||||
<div id="single-map"></div>
|
||||
|
||||
<div id="popup" class="ol-popup">
|
||||
<a
|
||||
href="#"
|
||||
id="popup-closer"
|
||||
style="
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
text-decoration: none;
|
||||
"
|
||||
>×</a
|
||||
>
|
||||
<div id="popup-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let map;
|
||||
// Global variables - only one set
|
||||
let theMap = null;
|
||||
let markerLayer = null;
|
||||
let popup = null;
|
||||
let initialized = false;
|
||||
|
||||
function focusMap() {
|
||||
// Center map example
|
||||
map.setCenter({ lat: 43.0896, lng: -79.0849 }); // Niagara Falls
|
||||
map.setZoom(12);
|
||||
// Clean initialization
|
||||
function initializeMap() {
|
||||
if (initialized || !window.ol) {
|
||||
console.log("Map already initialized or OpenLayers not ready");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Initializing single map...");
|
||||
|
||||
try {
|
||||
// Calgary coordinates
|
||||
const center = ol.proj.fromLonLat([-114.0719, 51.0447]);
|
||||
|
||||
// Create the ONE AND ONLY map
|
||||
theMap = new ol.Map({
|
||||
target: "single-map",
|
||||
layers: [
|
||||
new ol.layer.Tile({
|
||||
source: new ol.source.OSM(),
|
||||
}),
|
||||
],
|
||||
view: new ol.View({
|
||||
center: center,
|
||||
zoom: 11,
|
||||
}),
|
||||
});
|
||||
|
||||
// Create popup
|
||||
popup = new ol.Overlay({
|
||||
element: document.getElementById("popup"),
|
||||
positioning: "bottom-center",
|
||||
stopEvent: false,
|
||||
offset: [0, -50],
|
||||
});
|
||||
theMap.addOverlay(popup);
|
||||
|
||||
// Close popup handler
|
||||
document.getElementById("popup-closer").onclick = function () {
|
||||
popup.setPosition(undefined);
|
||||
return false;
|
||||
};
|
||||
|
||||
// Create marker layer
|
||||
markerLayer = new ol.layer.Vector({
|
||||
source: new ol.source.Vector(),
|
||||
style: new ol.style.Style({
|
||||
text: new ol.style.Text({
|
||||
text: "📍",
|
||||
font: "24px sans-serif",
|
||||
fill: new ol.style.Fill({ color: "#EF4444" }),
|
||||
offsetY: -12, // Adjust vertical position so pin points to location
|
||||
}),
|
||||
}),
|
||||
});
|
||||
theMap.addLayer(markerLayer);
|
||||
|
||||
// Click handler
|
||||
theMap.on("click", function (event) {
|
||||
const feature = theMap.forEachFeatureAtPixel(
|
||||
event.pixel,
|
||||
function (feature) {
|
||||
return feature;
|
||||
}
|
||||
);
|
||||
|
||||
if (feature && feature.get("address_data")) {
|
||||
const data = feature.get("address_data");
|
||||
document.getElementById("popup-content").innerHTML = `
|
||||
<div class="text-sm">
|
||||
<h4 class="font-semibold text-gray-900 mb-2">Address Details</h4>
|
||||
<p><strong>Address:</strong> ${data.address}</p>
|
||||
<p><strong>House #:</strong> ${data.house_number}</p>
|
||||
<p><strong>Street:</strong> ${data.street_name} ${data.street_type}</p>
|
||||
<p><strong>ID:</strong> ${data.address_id}</p>
|
||||
</div>
|
||||
`;
|
||||
popup.setPosition(event.coordinate);
|
||||
} else {
|
||||
popup.setPosition(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
console.log("Map initialized successfully");
|
||||
|
||||
// Load markers
|
||||
setTimeout(loadMarkers, 500);
|
||||
} catch (error) {
|
||||
console.error("Map initialization error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function initMap() {
|
||||
const niagaraFalls = { lat: 43.0896, lng: -79.0849 };
|
||||
// Load validated addresses
|
||||
async function loadMarkers() {
|
||||
try {
|
||||
const response = await fetch("/api/validated-addresses");
|
||||
const addresses = await response.json();
|
||||
|
||||
map = new google.maps.Map(document.getElementById("map"), {
|
||||
zoom: 12,
|
||||
center: niagaraFalls,
|
||||
});
|
||||
console.log(`Loading ${addresses.length} addresses`);
|
||||
document.getElementById(
|
||||
"marker-count"
|
||||
).textContent = `${addresses.length} on map`;
|
||||
|
||||
new google.maps.Marker({
|
||||
position: niagaraFalls,
|
||||
map,
|
||||
title: "Niagara Falls",
|
||||
});
|
||||
// Clear existing markers
|
||||
markerLayer.getSource().clear();
|
||||
|
||||
// Add new markers
|
||||
const features = [];
|
||||
addresses.forEach((addr) => {
|
||||
if (addr.longitude && addr.latitude) {
|
||||
const coords = ol.proj.fromLonLat([
|
||||
addr.longitude,
|
||||
addr.latitude,
|
||||
]);
|
||||
const feature = new ol.Feature({
|
||||
geometry: new ol.geom.Point(coords),
|
||||
address_data: addr,
|
||||
});
|
||||
features.push(feature);
|
||||
}
|
||||
});
|
||||
|
||||
markerLayer.getSource().addFeatures(features);
|
||||
|
||||
if (features.length > 0) {
|
||||
const extent = markerLayer.getSource().getExtent();
|
||||
theMap.getView().fit(extent, { padding: [20, 20, 20, 20] });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading markers:", error);
|
||||
document.getElementById("marker-count").textContent = "Error loading";
|
||||
}
|
||||
}
|
||||
|
||||
// Google Charts
|
||||
google.charts.load("current", { packages: ["corechart", "line"] });
|
||||
google.charts.setOnLoadCallback(drawAnalyticsChart);
|
||||
|
||||
function drawAnalyticsChart() {
|
||||
var data = new google.visualization.DataTable();
|
||||
data.addColumn("string", "Time");
|
||||
data.addColumn("number", "Visitors");
|
||||
data.addColumn("number", "Revenue");
|
||||
|
||||
data.addRows([
|
||||
["Jan", 4200, 32000],
|
||||
["Feb", 4800, 38000],
|
||||
["Mar", 5200, 42000],
|
||||
["Apr", 4900, 39000],
|
||||
["May", 5800, 45000],
|
||||
["Jun", 6200, 48000],
|
||||
]);
|
||||
|
||||
var options = {
|
||||
title: "Performance Over Time",
|
||||
backgroundColor: "transparent",
|
||||
hAxis: { title: "Month" },
|
||||
vAxis: { title: "Value" },
|
||||
colors: ["#3B82F6", "#10B981"],
|
||||
chartArea: {
|
||||
left: 60,
|
||||
top: 40,
|
||||
width: "90%",
|
||||
height: "70%",
|
||||
},
|
||||
legend: { position: "top", alignment: "center" },
|
||||
};
|
||||
|
||||
var chart = new google.visualization.LineChart(
|
||||
document.getElementById("analytics_chart")
|
||||
);
|
||||
chart.draw(data, options);
|
||||
// Control functions
|
||||
function refreshMap() {
|
||||
loadMarkers();
|
||||
}
|
||||
|
||||
function updateChart(type) {
|
||||
drawAnalyticsChart();
|
||||
function fitAllMarkers() {
|
||||
if (markerLayer && markerLayer.getSource().getFeatures().length > 0) {
|
||||
const extent = markerLayer.getSource().getExtent();
|
||||
theMap.getView().fit(extent, { padding: [20, 20, 20, 20] });
|
||||
}
|
||||
}
|
||||
|
||||
function clearAllMarkers() {
|
||||
if (markerLayer) {
|
||||
markerLayer.getSource().clear();
|
||||
}
|
||||
if (popup) {
|
||||
popup.setPosition(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when ready
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
setTimeout(initializeMap, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<script
|
||||
async
|
||||
defer
|
||||
src="https://maps.googleapis.com/maps/api/js?key=YOUR_KEY_HERE&callback=initMap"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
||||
|
||||
@@ -8,11 +8,20 @@
|
||||
class="w-full lg:w-1/2 flex flex-col gap-4 sm:gap-6 sticky top-0 self-start h-fit"
|
||||
>
|
||||
<!-- Today's Overview -->
|
||||
<div class="bg-white border-b border-gray-200">
|
||||
<div class="px-4 sm:px-6 py-4">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-4">
|
||||
<div class="bg-white border-b border-gray-200" x-data="{ open: true }">
|
||||
<div
|
||||
class="px-4 sm:px-6 py-4 flex justify-between items-center cursor-pointer"
|
||||
@click="open = !open"
|
||||
>
|
||||
<h3 class="text-sm font-semibold text-gray-900">
|
||||
Today's Overview
|
||||
</h3>
|
||||
<i
|
||||
class="fas"
|
||||
:class="open ? 'fa-chevron-up' : 'fa-chevron-down'"
|
||||
></i>
|
||||
</div>
|
||||
<div class="px-4 sm:px-6 pb-4" x-show="open" x-collapse>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -23,9 +32,9 @@
|
||||
</div>
|
||||
<span class="text-sm text-gray-700">Appointments Today</span>
|
||||
</div>
|
||||
<span class="text-lg font-semibold text-gray-900">
|
||||
{{ .Statistics.AppointmentsToday }}
|
||||
</span>
|
||||
<span class="text-lg font-semibold text-gray-900"
|
||||
>{{ .Statistics.AppointmentsToday }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -39,9 +48,9 @@
|
||||
>Appointments Tomorrow</span
|
||||
>
|
||||
</div>
|
||||
<span class="text-lg font-semibold text-gray-900">
|
||||
{{ .Statistics.AppointmentsTomorrow }}
|
||||
</span>
|
||||
<span class="text-lg font-semibold text-gray-900"
|
||||
>{{ .Statistics.AppointmentsTomorrow }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -53,20 +62,29 @@
|
||||
</div>
|
||||
<span class="text-sm text-gray-700">This Week</span>
|
||||
</div>
|
||||
<span class="text-lg font-semibold text-gray-900">
|
||||
{{ .Statistics.AppointmentsThisWeek }}
|
||||
</span>
|
||||
<span class="text-lg font-semibold text-gray-900"
|
||||
>{{ .Statistics.AppointmentsThisWeek }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Polling Progress -->
|
||||
<div class="bg-white border-b border-gray-200">
|
||||
<div class="px-4 sm:px-6 py-4">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-4">
|
||||
<div class="bg-white border-b border-gray-200" x-data="{ open: true }">
|
||||
<div
|
||||
class="px-4 sm:px-6 py-4 flex justify-between items-center cursor-pointer"
|
||||
@click="open = !open"
|
||||
>
|
||||
<h3 class="text-sm font-semibold text-gray-900">
|
||||
Polling Progress
|
||||
</h3>
|
||||
<i
|
||||
class="fas"
|
||||
:class="open ? 'fa-chevron-up' : 'fa-chevron-down'"
|
||||
></i>
|
||||
</div>
|
||||
<div class="px-4 sm:px-6 pb-4" x-show="open" x-collapse>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -77,9 +95,9 @@
|
||||
</div>
|
||||
<span class="text-sm text-gray-700">Polls Completed</span>
|
||||
</div>
|
||||
<span class="text-lg font-semibold text-green-600">
|
||||
{{ .Statistics.PollsCompleted }}
|
||||
</span>
|
||||
<span class="text-lg font-semibold text-green-600"
|
||||
>{{ .Statistics.PollsCompleted }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -91,9 +109,9 @@
|
||||
</div>
|
||||
<span class="text-sm text-gray-700">Polls Remaining</span>
|
||||
</div>
|
||||
<span class="text-lg font-semibold text-orange-600">
|
||||
{{ .Statistics.PollsRemaining }}
|
||||
</span>
|
||||
<span class="text-lg font-semibold text-orange-600"
|
||||
>{{ .Statistics.PollsRemaining }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
@@ -117,6 +135,44 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Team Members -->
|
||||
<div class="bg-white border-b border-gray-200" x-data="{ open: true }">
|
||||
<div
|
||||
class="px-4 sm:px-6 py-4 flex justify-between items-center cursor-pointer"
|
||||
@click="open = !open"
|
||||
>
|
||||
<h3 class="text-sm font-semibold text-gray-900">Team Members</h3>
|
||||
<i
|
||||
class="fas"
|
||||
:class="open ? 'fa-chevron-up' : 'fa-chevron-down'"
|
||||
></i>
|
||||
</div>
|
||||
<div class="px-4 sm:px-6 pb-4" x-show="open" x-collapse>
|
||||
<div class="space-y-3">
|
||||
{{ range .Teammates }}
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">
|
||||
{{ .FullName }} {{ if .IsLead }}
|
||||
<span class="ml-2 text-xs text-blue-600 font-semibold"
|
||||
>{{ .Role }}</span
|
||||
>
|
||||
{{ else }}
|
||||
<span class="ml-2 text-xs text-gray-500">{{ .Role }}</span>
|
||||
{{ end }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-sm text-gray-700">
|
||||
<i class="fas fa-phone mr-1 text-gray-500"></i>{{ .Phone }}
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
<p class="text-gray-500 text-sm">No teammates found</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right Column - Statistics -->
|
||||
<div class="flex-1 lg:flex-none lg:w-1/2 overflow-y-auto pr-2">
|
||||
|
||||
Reference in New Issue
Block a user