changing some things for reports section

This commit is contained in:
Mann Patel
2025-09-01 17:32:00 -06:00
parent e071ced77f
commit 76744e81cb
16 changed files with 411461 additions and 1587 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -52,7 +52,7 @@
<a href="/posts" class="text-sm font-medium {{if eq .ActiveSection "post"}}text-blue-300 border-b-2 border-blue-300{{else}}text-gray-300 hover:text-white{{end}} py-3 px-1">
Posts
</a>
<a href="/reports" class="text-sm font-medium {{if eq .ActiveSection "report"}}text-blue-300 border-b-2 border-blue-300{{else}}text-gray-300 hover:text-white{{end}} py-3 px-1">
<a href="/smart-reports" class="text-sm font-medium {{if eq .ActiveSection "report"}}text-blue-300 border-b-2 border-blue-300{{else}}text-gray-300 hover:text-white{{end}} py-3 px-1">
Reports
</a>
{{ end }}

View File

@@ -1,831 +0,0 @@
{{ define "content" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{.Title}}</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet" />
<script src="https://cdn.tailwindcss.com"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
</head>
<body class="bg-gray-50">
<!-- Full Width Container -->
<div class="min-h-screen w-full flex flex-col">
<!-- Main Reports 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-line text-white text-sm"></i>
</div>
<span class="text-xl font-semibold text-gray-900">
Reports & Analytics
</span>
</div>
<div class="flex items-center gap-4">
{{if .SearchType}}
<button onclick="exportData()" class="px-6 py-2.5 bg-green-600 text-white text-sm font-medium hover:bg-green-700 transition-colors">
<i class="fas fa-download mr-2"></i>Export CSV
</button>
{{end}}
<button onclick="toggleAdvancedSearch()" class="px-6 py-2.5 bg-purple-600 text-white text-sm font-medium hover:bg-purple-700 transition-colors">
<i class="fas fa-search-plus mr-2"></i>Advanced Search
</button>
<button onclick="printReport()" 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-print mr-2"></i>Print Report
</button>
</div>
</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">
<!-- Total Users -->
<div class="border-r border-gray-200 p-8 hover:bg-gray-50 transition-colors cursor-pointer" onclick="applyQuickFilter('users', {})">
<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">Total Users</p>
<p class="text-2xl font-bold text-gray-900">{{.ReportData.TotalUsers}}</p>
</div>
</div>
</div>
<!-- Total Polls -->
<div class="border-r border-gray-200 p-8 hover:bg-gray-50 transition-colors cursor-pointer" onclick="applyQuickFilter('polls', {})">
<div class="flex items-center">
<div class="w-12 h-12 bg-blue-50 flex items-center justify-center">
<i class="fas fa-poll text-blue-600 text-lg"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600 mb-1">Total Polls</p>
<p class="text-2xl font-bold text-gray-900">{{.ReportData.TotalPolls}}</p>
</div>
</div>
</div>
<!-- Total Addresses -->
<div class="border-r border-gray-200 p-8 hover:bg-gray-50 transition-colors cursor-pointer" onclick="applyQuickFilter('addresses', {})">
<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">Total Addresses</p>
<p class="text-2xl font-bold text-gray-900">{{.ReportData.TotalAddresses}}</p>
</div>
</div>
</div>
<!-- Total Appointments -->
<div class="p-8 hover:bg-gray-50 transition-colors cursor-pointer" onclick="applyQuickFilter('appointments', {})">
<div class="flex items-center">
<div class="w-12 h-12 bg-blue-50 flex items-center justify-center">
<i class="fas fa-calendar text-blue-600 text-lg"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-600 mb-1">Appointments</p>
<p class="text-2xl font-bold text-gray-900">{{len .ReportData.Appointments}}</p>
</div>
</div>
</div>
</div>
<!-- Search and Filter 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">Search & Filter Data</h3>
<form method="GET" action="/reports" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Search Type -->
<div>
<label for="search_type" class="block text-sm font-medium text-gray-700 mb-2">Search In</label>
<select name="search_type" id="search_type" class="w-full px-3 py-2 border border-gray-300 bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="" {{if eq .SearchType ""}}selected{{end}}>All Data</option>
<option value="users" {{if eq .SearchType "users"}}selected{{end}}>Users</option>
<option value="polls" {{if eq .SearchType "polls"}}selected{{end}}>Polls</option>
<option value="appointments" {{if eq .SearchType "appointments"}}selected{{end}}>Appointments</option>
<option value="addresses" {{if eq .SearchType "addresses"}}selected{{end}}>Addresses</option>
<option value="teams" {{if eq .SearchType "teams"}}selected{{end}}>Teams</option>
</select>
</div>
<!-- Search Query -->
<div>
<label for="search_query" class="block text-sm font-medium text-gray-700 mb-2">Search Term</label>
<input type="text" name="search_query" id="search_query" value="{{.SearchQuery}}"
placeholder="Enter search term..."
class="w-full px-3 py-2 border border-gray-300 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<!-- Role Filter -->
<div>
<label for="role_filter" class="block text-sm font-medium text-gray-700 mb-2">Role Filter</label>
<select name="role_filter" id="role_filter" class="w-full px-3 py-2 border border-gray-300 bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">All Roles</option>
<option value="1" {{if eq .RoleFilter "1"}}selected{{end}}>Admin</option>
<option value="2" {{if eq .RoleFilter "2"}}selected{{end}}>Volunteer</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
<!-- Date Range -->
<div>
<label for="date_from" class="block text-sm font-medium text-gray-700 mb-2">Date From</label>
<input type="date" name="date_from" id="date_from" value="{{.DateFrom}}"
class="w-full px-3 py-2 border border-gray-300 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="date_to" class="block text-sm font-medium text-gray-700 mb-2">Date To</label>
<input type="date" name="date_to" id="date_to" value="{{.DateTo}}"
class="w-full px-3 py-2 border border-gray-300 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<!-- Status Filter -->
<div>
<label for="status_filter" class="block text-sm font-medium text-gray-700 mb-2">Status Filter</label>
<select name="status_filter" id="status_filter" class="w-full px-3 py-2 border border-gray-300 bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">All Status</option>
<option value="active" {{if eq .StatusFilter "active"}}selected{{end}}>Active</option>
<option value="inactive" {{if eq .StatusFilter "inactive"}}selected{{end}}>Inactive</option>
</select>
</div>
<!-- Sort Options -->
<div>
<label for="sort_by" class="block text-sm font-medium text-gray-700 mb-2">Sort By</label>
<select name="sort_by" id="sort_by" class="w-full px-3 py-2 border border-gray-300 bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="created_at" {{if eq .SortBy "created_at"}}selected{{end}}>Created Date</option>
<option value="updated_at" {{if eq .SortBy "updated_at"}}selected{{end}}>Updated Date</option>
<option value="first_name" {{if eq .SortBy "first_name"}}selected{{end}}>First Name</option>
<option value="last_name" {{if eq .SortBy "last_name"}}selected{{end}}>Last Name</option>
<option value="email" {{if eq .SortBy "email"}}selected{{end}}>Email</option>
<option value="poll_title" {{if eq .SortBy "poll_title"}}selected{{end}}>Poll Title</option>
<option value="amount_donated" {{if eq .SortBy "amount_donated"}}selected{{end}}>Amount Donated</option>
</select>
</div>
</div>
<div class="flex gap-4">
<button type="submit" 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-search mr-2"></i>Search
</button>
<a href="/reports" class="px-6 py-2.5 bg-gray-600 text-white text-sm font-medium hover:bg-gray-700 transition-colors">
<i class="fas fa-times mr-2"></i>Clear Filters
</a>
</div>
</form>
</div>
</div>
<!-- Results Section - Full Width -->
{{if eq .SearchType "users"}}
<div class="bg-white border-t border-gray-200 w-full">
<div class="px-8 py-6">
<h3 class="text-lg font-semibold text-gray-900 mb-6">Users Results ({{.ReportData.TotalUsers}} total)</h3>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-200">
<th class="text-left py-3 text-sm font-medium text-gray-600">User</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Email</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Phone</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Role</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Admin Code</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Created</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{{range .ReportData.Users}}
<tr class="hover:bg-gray-50 transition-colors">
<td class="py-4">
<div class="text-sm font-medium text-gray-900">{{.FirstName}} {{.LastName}}</div>
<div class="text-sm text-gray-500">ID: {{.UserID}}</div>
</td>
<td class="py-4 text-sm text-gray-900">{{.Email}}</td>
<td class="py-4 text-sm text-gray-900">{{.Phone}}</td>
<td class="py-4">
<span class="px-2 py-1 text-xs font-semibold rounded-full {{if eq .RoleID 1}}bg-red-100 text-red-800{{else}}bg-green-100 text-green-800{{end}}">
{{if eq .RoleID 1}}Admin{{else}}Volunteer{{end}}
</span>
</td>
<td class="py-4 text-sm text-gray-900">{{.AdminCode}}</td>
<td class="py-4 text-sm text-gray-500">{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{else if eq .SearchType "polls"}}
<div class="bg-white border-t border-gray-200 w-full">
<div class="px-8 py-6">
<h3 class="text-lg font-semibold text-gray-900 mb-6">Polls Results ({{.ReportData.TotalPolls}} total)</h3>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-200">
<th class="text-left py-3 text-sm font-medium text-gray-600">Poll</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Author</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Address</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Status</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Donated</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Created</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{{range .ReportData.Polls}}
<tr class="hover:bg-gray-50 transition-colors">
<td class="py-4">
<div class="text-sm font-medium text-gray-900">{{.PollTitle}}</div>
<div class="text-sm text-gray-500">{{.PollDescription}}</div>
</td>
<td class="py-4 text-sm text-gray-900">{{.AuthorName}}</td>
<td class="py-4 text-sm text-gray-900">{{.Address}}</td>
<td class="py-4">
<span class="px-2 py-1 text-xs font-semibold rounded-full {{if .IsActive}}bg-green-100 text-green-800{{else}}bg-red-100 text-red-800{{end}}">
{{if .IsActive}}Active{{else}}Inactive{{end}}
</span>
</td>
<td class="py-4 text-sm text-gray-900">${{printf "%.2f" .AmountDonated}}</td>
<td class="py-4 text-sm text-gray-500">{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{else if eq .SearchType "appointments"}}
<div class="bg-white border-t border-gray-200 w-full">
<div class="px-8 py-6">
<h3 class="text-lg font-semibold text-gray-900 mb-6">Appointments Results ({{len .ReportData.Appointments}} total)</h3>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-200">
<th class="text-left py-3 text-sm font-medium text-gray-600">User</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Address</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Date</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Time</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Created</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{{range .ReportData.Appointments}}
<tr class="hover:bg-gray-50 transition-colors">
<td class="py-4 text-sm font-medium text-gray-900">{{.UserName}}</td>
<td class="py-4 text-sm text-gray-900">{{.Address}}</td>
<td class="py-4 text-sm text-gray-900">{{.AppointmentDate.Format "2006-01-02"}}</td>
<td class="py-4 text-sm text-gray-900">{{.AppointmentTime.Format "15:04"}}</td>
<td class="py-4 text-sm text-gray-500">{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{else if eq .SearchType "addresses"}}
<div class="bg-white border-t border-gray-200 w-full">
<div class="px-8 py-6">
<h3 class="text-lg font-semibold text-gray-900 mb-6">Addresses Results ({{.ReportData.TotalAddresses}} total)</h3>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-200">
<th class="text-left py-3 text-sm font-medium text-gray-600">Address</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Street Details</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Coordinates</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Visited</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Created</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{{range .ReportData.Addresses}}
<tr class="hover:bg-gray-50 transition-colors">
<td class="py-4">
<div class="text-sm font-medium text-gray-900">{{.Address}}</div>
<div class="text-sm text-gray-500">{{.HouseNumber}}{{.HouseAlpha}}</div>
</td>
<td class="py-4">
<div class="text-sm text-gray-900">{{.StreetName}} {{.StreetType}}</div>
<div class="text-sm text-gray-500">{{.StreetQuadrant}}</div>
</td>
<td class="py-4 text-sm text-gray-900">{{printf "%.6f" .Latitude}}, {{printf "%.6f" .Longitude}}</td>
<td class="py-4">
<span class="px-2 py-1 text-xs font-semibold rounded-full {{if .VisitedValidated}}bg-green-100 text-green-800{{else}}bg-red-100 text-red-800{{end}}">
{{if .VisitedValidated}}Visited{{else}}Not Visited{{end}}
</span>
</td>
<td class="py-4 text-sm text-gray-500">{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{else if eq .SearchType "teams"}}
<div class="bg-white border-t border-gray-200 w-full">
<div class="px-8 py-6">
<h3 class="text-lg font-semibold text-gray-900 mb-6">Teams Results ({{len .ReportData.Teams}} total)</h3>
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-200">
<th class="text-left py-3 text-sm font-medium text-gray-600">Team ID</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Team Lead</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Volunteer</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Created</th>
<th class="text-left py-3 text-sm font-medium text-gray-600">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{{range .ReportData.Teams}}
<tr class="hover:bg-gray-50 transition-colors">
<td class="py-4 text-sm font-medium text-gray-900">{{.TeamID}}</td>
<td class="py-4 text-sm text-gray-900">{{.TeamLeadName}}</td>
<td class="py-4 text-sm text-gray-900">{{.VolunteerName}}</td>
<td class="py-4 text-sm text-gray-500">{{.CreatedAt.Format "2006-01-02 15:04"}}</td>
<td class="py-4 text-sm font-medium">
<a href="/teams/{{.TeamID}}" class="text-blue-600 hover:text-blue-800">View Details</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
{{else}}
<!-- Summary Dashboard View -->
<div class="bg-white border-t border-gray-200 w-full">
<div class="px-8 py-6">
<h3 class="text-lg font-semibold text-gray-900 mb-6">Data Overview</h3>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Recent Users -->
<div class="border border-gray-200">
<div class="p-6 border-b border-gray-200">
<div class="flex justify-between items-center">
<h4 class="text-base font-medium text-gray-900">Recent Users</h4>
<a href="/reports?search_type=users" class="text-blue-600 hover:text-blue-800 text-sm">View All</a>
</div>
</div>
<div class="p-6">
{{if .ReportData.Users}}
<div class="space-y-4">
{{range .ReportData.Users}}
<div class="flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors">
<div>
<div class="text-sm font-medium text-gray-900">{{.FirstName}} {{.LastName}}</div>
<div class="text-xs text-gray-500">{{.Email}}</div>
</div>
<div class="text-right">
<div class="text-xs text-gray-500">{{.CreatedAt.Format "Jan 02, 2006"}}</div>
<span class="px-2 py-1 text-xs font-semibold rounded-full {{if eq .RoleID 1}}bg-red-100 text-red-800{{else}}bg-green-100 text-green-800{{end}}">
{{if eq .RoleID 1}}Admin{{else}}Volunteer{{end}}
</span>
</div>
</div>
{{end}}
</div>
{{else}}
<p class="text-gray-500 text-center py-8">No users found</p>
{{end}}
</div>
</div>
<!-- Recent Polls -->
<div class="border border-gray-200">
<div class="p-6 border-b border-gray-200">
<div class="flex justify-between items-center">
<h4 class="text-base font-medium text-gray-900">Recent Polls</h4>
<a href="/reports?search_type=polls" class="text-blue-600 hover:text-blue-800 text-sm">View All</a>
</div>
</div>
<div class="p-6">
{{if .ReportData.Polls}}
<div class="space-y-4">
{{range .ReportData.Polls}}
<div class="flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors">
<div>
<div class="text-sm font-medium text-gray-900">{{.PollTitle}}</div>
<div class="text-xs text-gray-500">by {{.AuthorName}}</div>
</div>
<div class="text-right">
<div class="text-xs text-gray-500">{{.CreatedAt.Format "Jan 02, 2006"}}</div>
<span class="px-2 py-1 text-xs font-semibold rounded-full {{if .IsActive}}bg-green-100 text-green-800{{else}}bg-red-100 text-red-800{{end}}">
{{if .IsActive}}Active{{else}}Inactive{{end}}
</span>
</div>
</div>
{{end}}
</div>
{{else}}
<p class="text-gray-500 text-center py-8">No polls found</p>
{{end}}
</div>
</div>
<!-- Recent Appointments -->
<div class="border border-gray-200">
<div class="p-6 border-b border-gray-200">
<div class="flex justify-between items-center">
<h4 class="text-base font-medium text-gray-900">Recent Appointments</h4>
<a href="/reports?search_type=appointments" class="text-blue-600 hover:text-blue-800 text-sm">View All</a>
</div>
</div>
<div class="p-6">
{{if .ReportData.Appointments}}
<div class="space-y-4">
{{range .ReportData.Appointments}}
<div class="flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors">
<div>
<div class="text-sm font-medium text-gray-900">{{.UserName}}</div>
<div class="text-xs text-gray-500">{{.Address}}</div>
</div>
<div class="text-right">
<div class="text-xs text-gray-900">{{.AppointmentDate.Format "Jan 02, 2006"}}</div>
<div class="text-xs text-gray-500">{{.AppointmentTime.Format "15:04"}}</div>
</div>
</div>
{{end}}
</div>
{{else}}
<p class="text-gray-500 text-center py-8">No appointments found</p>
{{end}}
</div>
</div>
<!-- Address Summary -->
<div class="border border-gray-200">
<div class="p-6 border-b border-gray-200">
<div class="flex justify-between items-center">
<h4 class="text-base font-medium text-gray-900">Address Summary</h4>
<a href="/reports?search_type=addresses" class="text-blue-600 hover:text-blue-800 text-sm">View All</a>
</div>
</div>
<div class="p-6">
{{if .ReportData.Addresses}}
<div class="space-y-4">
{{range .ReportData.Addresses}}
<div class="flex items-center justify-between p-4 bg-gray-50 hover:bg-gray-100 transition-colors">
<div>
<div class="text-sm font-medium text-gray-900">{{.Address}}</div>
<div class="text-xs text-gray-500">{{.StreetName}} {{.StreetType}}, {{.StreetQuadrant}}</div>
</div>
<div class="text-right">
<span class="px-2 py-1 text-xs font-semibold rounded-full {{if .VisitedValidated}}bg-green-100 text-green-800{{else}}bg-red-100 text-red-800{{end}}">
{{if .VisitedValidated}}Visited{{else}}Not Visited{{end}}
</span>
</div>
</div>
{{end}}
</div>
{{else}}
<p class="text-gray-500 text-center py-8">No addresses found</p>
{{end}}
</div>
</div>
</div>
</div>
</div>
<!-- Analytics Chart Section - Full Width -->
<div class="bg-white border-t border-gray-200 w-full">
<div class="px-8 py-6">
<h3 class="text-lg font-semibold text-gray-900 mb-6">Analytics Overview</h3>
<div id="analytics_chart" class="w-full h-[400px]"></div>
</div>
</div>
{{end}}
<!-- Pagination -->
{{if and .SearchType (or (gt .ReportData.TotalUsers 0) (gt .ReportData.TotalPolls 0) (gt .ReportData.TotalAddresses 0))}}
<div class="bg-white border-t border-gray-200 w-full">
<div class="px-8 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<span class="text-sm text-gray-700">
Page {{.CurrentPage}} | {{.Limit}} per page
</span>
</div>
<div class="flex items-center gap-2">
{{if gt .CurrentPage 1}}
<a href="?{{.Request.URL.RawQuery}}&page={{sub .CurrentPage 1}}" class="px-3 py-2 border border-gray-300 text-gray-700 text-sm hover:bg-gray-50 transition-colors">
<i class="fas fa-chevron-left"></i>
</a>
{{end}}
<span class="px-3 py-2 bg-blue-50 text-blue-600 text-sm font-medium border border-blue-200">
{{.CurrentPage}}
</span>
<a href="?{{.Request.URL.RawQuery}}&page={{add .CurrentPage 1}}" class="px-3 py-2 border border-gray-300 text-gray-700 text-sm hover:bg-gray-50 transition-colors">
<i class="fas fa-chevron-right"></i>
</a>
</div>
</div>
</div>
</div>
{{end}}
</div>
</div>
</div>
<!-- Advanced Search Modal -->
<div id="advancedSearchModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden z-50">
<div class="relative top-20 mx-auto p-5 border w-11/12 md:w-3/4 lg:w-1/2 shadow-lg bg-white">
<div class="mt-3">
<div class="flex justify-between items-center mb-6">
<h3 class="text-lg font-semibold text-gray-900">Advanced Search</h3>
<button onclick="toggleAdvancedSearch()" class="text-gray-400 hover:text-gray-600 text-xl">
<i class="fas fa-times"></i>
</button>
</div>
<form method="POST" action="/reports/advanced-search" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Entity Type</label>
<select name="entity" class="w-full px-3 py-2 border border-gray-300 bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="users">Users</option>
<option value="polls">Polls</option>
<option value="appointments">Appointments</option>
<option value="addresses">Addresses</option>
<option value="teams">Teams</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Text Search</label>
<input type="text" name="text_search" placeholder="Search in names, titles, descriptions..."
class="w-full px-3 py-2 border border-gray-300 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Date From</label>
<input type="date" name="date_from"
class="w-full px-3 py-2 border border-gray-300 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Date To</label>
<input type="date" name="date_to"
class="w-full px-3 py-2 border border-gray-300 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Role Filter</label>
<select name="role_filter" class="w-full px-3 py-2 border border-gray-300 bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">All Roles</option>
<option value="1">Admin</option>
<option value="2">Volunteer</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Status Filter</label>
<select name="status_filter" class="w-full px-3 py-2 border border-gray-300 bg-white text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<option value="">All Status</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Min Amount (Polls)</label>
<input type="number" name="amount_min" step="0.01" placeholder="0.00"
class="w-full px-3 py-2 border border-gray-300 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Max Amount (Polls)</label>
<input type="number" name="amount_max" step="0.01" placeholder="1000.00"
class="w-full px-3 py-2 border border-gray-300 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
</div>
</div>
<div class="flex justify-end gap-3 pt-6">
<button type="button" onclick="toggleAdvancedSearch()" class="px-6 py-2.5 bg-gray-300 text-gray-800 text-sm font-medium hover:bg-gray-400 transition-colors">
Cancel
</button>
<button type="submit" 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-search mr-2"></i>Search
</button>
</div>
</form>
</div>
</div>
</div>
<script>
// Advanced search modal toggle
function toggleAdvancedSearch() {
const modal = document.getElementById('advancedSearchModal');
modal.classList.toggle('hidden');
}
// Print report functionality
function printReport() {
window.print();
}
// Quick filter application
function applyQuickFilter(type, filter) {
const url = new URL(window.location);
url.searchParams.set('search_type', type);
if (filter) {
Object.keys(filter).forEach(key => {
url.searchParams.set(key, filter[key]);
});
}
window.location.href = url.toString();
}
// Export data with current filters
function exportData() {
const url = new URL('/reports/export', window.location.origin);
const currentParams = new URLSearchParams(window.location.search);
currentParams.forEach((value, key) => {
url.searchParams.set(key, value);
});
window.location.href = url.toString();
}
// Auto-submit form when search type changes
document.addEventListener('DOMContentLoaded', function() {
const searchTypeSelect = document.getElementById('search_type');
const searchInput = document.getElementById('search_query');
// Update placeholder based on search type
searchTypeSelect.addEventListener('change', function() {
const placeholders = {
'users': 'Search by name, email...',
'polls': 'Search by title, description...',
'appointments': 'Search by user name, address...',
'addresses': 'Search by address, street name...',
'teams': 'Search by team lead, volunteer name...',
'': 'Enter search term...'
};
searchInput.placeholder = placeholders[this.value] || 'Enter search term...';
});
// Close modal when clicking outside
document.getElementById('advancedSearchModal').addEventListener('click', function(e) {
if (e.target === this) {
toggleAdvancedSearch();
}
});
});
// Google Charts for analytics
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", "Users");
data.addColumn("number", "Polls");
data.addColumn("number", "Addresses");
// Sample data - you can replace this with real data from your backend
data.addRows([
["Jan", {{if .ReportData.TotalUsers}}{{.ReportData.TotalUsers}}{{else}}120{{end}}, {{if .ReportData.TotalPolls}}{{.ReportData.TotalPolls}}{{else}}45{{end}}, {{if .ReportData.TotalAddresses}}{{.ReportData.TotalAddresses}}{{else}}380{{end}}],
["Feb", {{if .ReportData.TotalUsers}}{{add .ReportData.TotalUsers 50}}{{else}}180{{end}}, {{if .ReportData.TotalPolls}}{{add .ReportData.TotalPolls 15}}{{else}}62{{end}}, {{if .ReportData.TotalAddresses}}{{add .ReportData.TotalAddresses 120}}{{else}}420{{end}}],
["Mar", {{if .ReportData.TotalUsers}}{{add .ReportData.TotalUsers 80}}{{else}}220{{end}}, {{if .ReportData.TotalPolls}}{{add .ReportData.TotalPolls 25}}{{else}}78{{end}}, {{if .ReportData.TotalAddresses}}{{add .ReportData.TotalAddresses 200}}{{else}}510{{end}}],
["Apr", {{if .ReportData.TotalUsers}}{{add .ReportData.TotalUsers 120}}{{else}}280{{end}}, {{if .ReportData.TotalPolls}}{{add .ReportData.TotalPolls 35}}{{else}}95{{end}}, {{if .ReportData.TotalAddresses}}{{add .ReportData.TotalAddresses 280}}{{else}}650{{end}}],
["May", {{if .ReportData.TotalUsers}}{{add .ReportData.TotalUsers 150}}{{else}}320{{end}}, {{if .ReportData.TotalPolls}}{{add .ReportData.TotalPolls 42}}{{else}}108{{end}}, {{if .ReportData.TotalAddresses}}{{add .ReportData.TotalAddresses 350}}{{else}}720{{end}}],
["Jun", {{if .ReportData.TotalUsers}}{{add .ReportData.TotalUsers 200}}{{else}}380{{end}}, {{if .ReportData.TotalPolls}}{{add .ReportData.TotalPolls 58}}{{else}}125{{end}}, {{if .ReportData.TotalAddresses}}{{add .ReportData.TotalAddresses 420}}{{else}}800{{end}}],
]);
var options = {
title: "System Growth Over Time",
backgroundColor: "transparent",
hAxis: { title: "Month" },
vAxis: { title: "Count" },
colors: ["#3B82F6", "#10B981", "#F59E0B"],
chartArea: {
left: 60,
top: 40,
width: "90%",
height: "70%",
},
legend: { position: "top", alignment: "center" },
lineWidth: 3,
pointSize: 5,
};
var chart = new google.visualization.LineChart(
document.getElementById("analytics_chart")
);
chart.draw(data, options);
}
function updateChart(type) {
drawAnalyticsChart();
}
// Real-time search functionality
let searchTimeout;
function handleSearchInput() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(function() {
// You can implement real-time search here
console.log('Performing search...');
}, 500);
}
// Quick stats click handlers
function focusUsers() {
applyQuickFilter('users', {});
}
function focusPolls() {
applyQuickFilter('polls', {});
}
function focusAddresses() {
applyQuickFilter('addresses', {});
}
function focusAppointments() {
applyQuickFilter('appointments', {});
}
</script>
<style>
@media print {
.no-print {
display: none !important;
}
.print-only {
display: block !important;
}
body {
background: white !important;
}
.border-gray-200 {
border-color: #000 !important;
}
}
/* Enhanced hover effects matching dashboard style */
.hover-highlight:hover {
background-color: #f7fafc;
transform: translateY(-1px);
transition: all 0.2s ease;
}
/* Custom scrollbar for tables */
.overflow-x-auto::-webkit-scrollbar {
height: 6px;
}
.overflow-x-auto::-webkit-scrollbar-track {
background: #f1f5f9;
}
.overflow-x-auto::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
.overflow-x-auto::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
</style>
</body>
</html>
{{ end }}

View File

@@ -0,0 +1,752 @@
{{ define "content" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{.Title}}</title>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
rel="stylesheet"
/>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50">
<div class="min-h-screen w-full flex flex-col">
<!-- Header -->
<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-purple-600 flex items-center justify-center rounded"
>
<i class="fas fa-brain text-white text-sm"></i>
</div>
<span class="text-xl font-semibold text-gray-900"
>Smart Reports & Analytics</span
>
</div>
<div class="flex items-center gap-4">
{{if .SmartQuery}}
<button
onclick="exportResults()"
class="px-6 py-2.5 bg-green-600 text-white text-sm font-medium rounded hover:bg-green-700 transition-colors"
>
<i class="fas fa-download mr-2"></i>Export Results
</button>
{{end}}
<button
onclick="clearQuery()"
class="px-6 py-2.5 border border-gray-300 text-gray-700 text-sm font-medium rounded hover:bg-gray-50 transition-colors"
>
<i class="fas fa-eraser mr-2"></i>Clear
</button>
</div>
</div>
</div>
</div>
<!-- Smart Search Interface -->
<div class="bg-white w-full border-b border-gray-200">
<div class="px-8 py-8">
<div class="max-w-4xl mx-auto">
<h2 class="text-2xl font-bold text-gray-900 mb-2">
Ask About Your Data
</h2>
<p class="text-gray-600 mb-8">
Use natural language to query across users, polls, appointments,
addresses, and teams
</p>
<form method="GET" action="/smart-reports" class="mb-8">
<div class="relative">
<div
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
>
<i class="fas fa-magic text-purple-400"></i>
</div>
<input
type="text"
name="smart_query"
value="{{.SmartQuery}}"
placeholder="e.g., 'volunteers who went to Main Street' or 'donations by team 5'"
class="w-full pl-10 pr-4 py-4 text-lg border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
autocomplete="off"
/>
</div>
<div class="flex justify-between items-center mt-4">
<button
type="submit"
class="px-8 py-3 bg-purple-600 text-white font-semibold rounded-lg hover:bg-purple-700 transition-colors"
>
<i class="fas fa-search mr-2"></i>Search
</button>
<button
type="button"
onclick="toggleExamples()"
class="text-purple-600 hover:text-purple-800 text-sm font-medium"
>
<i class="fas fa-lightbulb mr-1"></i>Show Examples
</button>
</div>
</form>
<!-- Query Examples -->
<div id="queryExamples" class="hidden">
<h3 class="text-lg font-semibold text-gray-900 mb-4">
Example Queries
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
{{range .QueryExamples}}
<div
class="p-4 bg-gray-50 rounded-lg border border-gray-200 hover:bg-gray-100 transition-colors cursor-pointer"
onclick="useExample('{{.}}')"
>
<div class="flex items-center justify-between">
<span class="text-sm text-gray-700">{{.}}</span>
<i class="fas fa-arrow-right text-purple-500 text-xs"></i>
</div>
</div>
{{end}}
</div>
</div>
</div>
</div>
</div>
<!-- Results Section -->
{{if .Result}} {{if .Result.Error}}
<div class="bg-white w-full">
<div class="px-8 py-6">
<div class="bg-red-50 border border-red-200 rounded-lg p-6">
<div class="flex items-center">
<div
class="w-8 h-8 bg-red-100 rounded-full flex items-center justify-center"
>
<i class="fas fa-exclamation-triangle text-red-600 text-sm"></i>
</div>
<div class="ml-4">
<h3 class="text-lg font-semibold text-red-800">Query Error</h3>
<p class="text-red-700 mt-2">{{.Result.Error}}</p>
{{if .Result.Query}}
<details class="mt-4">
<summary class="text-sm text-red-600 cursor-pointer">
Show Generated SQL
</summary>
<pre
class="mt-2 p-3 bg-red-100 text-red-800 text-xs rounded overflow-x-auto"
>
{{.Result.Query}}</pre
>
</details>
{{end}}
</div>
</div>
</div>
</div>
</div>
{{else}}
<!-- Successful Results -->
<div class="bg-white w-full">
<div class="px-8 py-6">
<div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-semibold text-gray-900">
Query Results ({{.Result.Count}} records found)
</h3>
<div class="flex items-center gap-4">
<button
onclick="toggleQueryDetails()"
class="text-sm text-gray-500 hover:text-gray-700"
>
<i class="fas fa-code mr-1"></i>Show SQL
</button>
<button
onclick="exportResults()"
class="px-4 py-2 bg-green-600 text-white text-sm rounded hover:bg-green-700 transition-colors"
>
<i class="fas fa-download mr-1"></i>Export CSV
</button>
</div>
</div>
<!-- Query Details (Hidden by default) -->
<div
id="queryDetails"
class="hidden mb-6 p-4 bg-gray-50 rounded-lg border"
>
<h4 class="text-sm font-semibold text-gray-700 mb-2">
Generated SQL Query:
</h4>
<pre class="text-xs text-gray-600 overflow-x-auto">
{{.Result.Query}}</pre
>
</div>
<!-- Results Table -->
{{if gt .Result.Count 0}}
<div class="overflow-x-auto rounded-lg border border-gray-200">
<table class="w-full">
<thead class="bg-gray-50">
<tr>
{{range .Result.Columns}}
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-200"
>
{{formatColumnName .}}
</th>
{{end}}
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{{range .Result.Rows}}
<tr class="hover:bg-gray-50 transition-colors">
{{range .}}
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{.}}
</td>
{{end}}
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<div class="text-center py-12">
<div
class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4"
>
<i class="fas fa-search text-gray-400 text-xl"></i>
</div>
<h3 class="text-lg font-medium text-gray-900 mb-2">
No Results Found
</h3>
<p class="text-gray-500">
Try adjusting your query or check the examples below
</p>
</div>
{{end}}
</div>
</div>
{{end}} {{end}}
<!-- Query Builder Assistant -->
<div class="bg-white w-full border-t border-gray-200">
<div class="px-8 py-6">
<div class="max-w-4xl mx-auto">
<h3 class="text-lg font-semibold text-gray-900 mb-6">
Smart Query Builder
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Common Queries -->
<div class="space-y-4">
<h4 class="font-medium text-gray-900">
User & Volunteer Queries
</h4>
<div class="space-y-2">
<button
onclick="useSmartQuery('volunteers who went to')"
class="w-full text-left p-3 bg-blue-50 text-blue-800 rounded border hover:bg-blue-100 transition-colors"
>
<i class="fas fa-user-friends mr-2"></i>Volunteers who went
to...
</button>
<button
onclick="useSmartQuery('users with role admin')"
class="w-full text-left p-3 bg-blue-50 text-blue-800 rounded border hover:bg-blue-100 transition-colors"
>
<i class="fas fa-user-shield mr-2"></i>Users by role
</button>
<button
onclick="useSmartQuery('volunteer activity by month')"
class="w-full text-left p-3 bg-blue-50 text-blue-800 rounded border hover:bg-blue-100 transition-colors"
>
<i class="fas fa-chart-line mr-2"></i>Volunteer activity
</button>
</div>
</div>
<!-- Poll & Donation Queries -->
<div class="space-y-4">
<h4 class="font-medium text-gray-900">
Poll & Donation Queries
</h4>
<div class="space-y-2">
<button
onclick="useSmartQuery('poll responses for')"
class="w-full text-left p-3 bg-green-50 text-green-800 rounded border hover:bg-green-100 transition-colors"
>
<i class="fas fa-poll mr-2"></i>Poll responses for address
</button>
<button
onclick="useSmartQuery('donations by volunteer')"
class="w-full text-left p-3 bg-green-50 text-green-800 rounded border hover:bg-green-100 transition-colors"
>
<i class="fas fa-donate mr-2"></i>Donations by volunteer
</button>
<button
onclick="useSmartQuery('active polls created after 2024-01-01')"
class="w-full text-left p-3 bg-green-50 text-green-800 rounded border hover:bg-green-100 transition-colors"
>
<i class="fas fa-calendar-check mr-2"></i>Active polls by
date
</button>
</div>
</div>
<!-- Team & Address Queries -->
<div class="space-y-4">
<h4 class="font-medium text-gray-900">
Team & Address Queries
</h4>
<div class="space-y-2">
<button
onclick="useSmartQuery('team with most appointments')"
class="w-full text-left p-3 bg-purple-50 text-purple-800 rounded border hover:bg-purple-100 transition-colors"
>
<i class="fas fa-users mr-2"></i>Top performing teams
</button>
<button
onclick="useSmartQuery('visited addresses')"
class="w-full text-left p-3 bg-purple-50 text-purple-800 rounded border hover:bg-purple-100 transition-colors"
>
<i class="fas fa-map-marked-alt mr-2"></i>Visited addresses
</button>
<button
onclick="useSmartQuery('money made by team')"
class="w-full text-left p-3 bg-purple-50 text-purple-800 rounded border hover:bg-purple-100 transition-colors"
>
<i class="fas fa-dollar-sign mr-2"></i>Team earnings
</button>
</div>
</div>
</div>
<!-- Query Syntax Help -->
<div class="mt-8 p-6 bg-gray-50 rounded-lg border border-gray-200">
<h4 class="font-semibold text-gray-900 mb-4">
<i class="fas fa-info-circle text-blue-500 mr-2"></i>Smart Query
Syntax Guide
</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 text-sm">
<div>
<h5 class="font-medium text-gray-800 mb-2">
Entity Keywords:
</h5>
<ul class="space-y-1 text-gray-600">
<li>
<code class="bg-gray-200 px-1 rounded"
>volunteer, user, admin</code
>
- User data
</li>
<li>
<code class="bg-gray-200 px-1 rounded">poll, polls</code>
- Poll data
</li>
<li>
<code class="bg-gray-200 px-1 rounded"
>address, addresses</code
>
- Address data
</li>
<li>
<code class="bg-gray-200 px-1 rounded"
>appointment, appointments</code
>
- Appointment data
</li>
<li>
<code class="bg-gray-200 px-1 rounded">team, teams</code>
- Team data
</li>
</ul>
</div>
<div>
<h5 class="font-medium text-gray-800 mb-2">
Action Keywords:
</h5>
<ul class="space-y-1 text-gray-600">
<li>
<code class="bg-gray-200 px-1 rounded"
>went to, visited</code
>
- Filter by visits
</li>
<li>
<code class="bg-gray-200 px-1 rounded"
>donated, money, amount</code
>
- Financial data
</li>
<li>
<code class="bg-gray-200 px-1 rounded"
>active, inactive</code
>
- Status filters
</li>
<li>
<code class="bg-gray-200 px-1 rounded"
>most, highest, top</code
>
- Ranking queries
</li>
<li>
<code class="bg-gray-200 px-1 rounded"
>from DATE, after DATE</code
>
- Date filters
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Live Query Suggestions -->
<div class="bg-white w-full border-t border-gray-200">
<div class="px-8 py-6">
<div class="max-w-4xl mx-auto">
<h3 class="text-lg font-semibold text-gray-900 mb-4">
Popular Analysis Questions
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
class="p-4 border border-gray-200 rounded-lg hover:border-purple-300 transition-colors cursor-pointer"
onclick="useSmartQuery('which volunteer went to most addresses')"
>
<h5 class="font-medium text-gray-900 mb-2">
Top Performing Volunteers
</h5>
<p class="text-sm text-gray-600">
Find volunteers with most address visits
</p>
</div>
<div
class="p-4 border border-gray-200 rounded-lg hover:border-purple-300 transition-colors cursor-pointer"
onclick="useSmartQuery('poll responses of visited addresses')"
>
<h5 class="font-medium text-gray-900 mb-2">
Visited Address Polls
</h5>
<p class="text-sm text-gray-600">
Polls from addresses that were visited
</p>
</div>
<div
class="p-4 border border-gray-200 rounded-lg hover:border-purple-300 transition-colors cursor-pointer"
onclick="useSmartQuery('donations filtered by volunteer Sarah')"
>
<h5 class="font-medium text-gray-900 mb-2">
Volunteer Donations
</h5>
<p class="text-sm text-gray-600">
Total donations by specific volunteer
</p>
</div>
<div
class="p-4 border border-gray-200 rounded-lg hover:border-purple-300 transition-colors cursor-pointer"
onclick="useSmartQuery('team that did most appointments')"
>
<h5 class="font-medium text-gray-900 mb-2">Most Active Team</h5>
<p class="text-sm text-gray-600">
Team with highest appointment count
</p>
</div>
<div
class="p-4 border border-gray-200 rounded-lg hover:border-purple-300 transition-colors cursor-pointer"
onclick="useSmartQuery('people in team 1')"
>
<h5 class="font-medium text-gray-900 mb-2">Team Members</h5>
<p class="text-sm text-gray-600">
View all members of a specific team
</p>
</div>
<div
class="p-4 border border-gray-200 rounded-lg hover:border-purple-300 transition-colors cursor-pointer"
onclick="useSmartQuery('unvisited addresses with polls')"
>
<h5 class="font-medium text-gray-900 mb-2">
Missed Opportunities
</h5>
<p class="text-sm text-gray-600">
Addresses with polls but no visits
</p>
</div>
</div>
</div>
</div>
</div>
<!-- Recent Queries History -->
<div class="bg-gray-50 w-full border-t border-gray-200">
<div class="px-8 py-6">
<div class="max-w-4xl mx-auto">
<h3 class="text-lg font-semibold text-gray-900 mb-4">
Quick Actions
</h3>
<div class="flex flex-wrap gap-3">
<button
onclick="useSmartQuery('all users created today')"
class="px-4 py-2 bg-white border border-gray-300 text-gray-700 text-sm rounded hover:bg-gray-50 transition-colors"
>
Today's Users
</button>
<button
onclick="useSmartQuery('donations over 50')"
class="px-4 py-2 bg-white border border-gray-300 text-gray-700 text-sm rounded hover:bg-gray-50 transition-colors"
>
High Donations
</button>
<button
onclick="useSmartQuery('appointments this week')"
class="px-4 py-2 bg-white border border-gray-300 text-gray-700 text-sm rounded hover:bg-gray-50 transition-colors"
>
This Week's Appointments
</button>
<button
onclick="useSmartQuery('inactive polls with donations')"
class="px-4 py-2 bg-white border border-gray-300 text-gray-700 text-sm rounded hover:bg-gray-50 transition-colors"
>
Inactive Paid Polls
</button>
<button
onclick="useSmartQuery('teams created this month')"
class="px-4 py-2 bg-white border border-gray-300 text-gray-700 text-sm rounded hover:bg-gray-50 transition-colors"
>
New Teams
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Toggle query examples
function toggleExamples() {
const examples = document.getElementById("queryExamples");
examples.classList.toggle("hidden");
}
// Use example query
function useExample(query) {
document.querySelector('input[name="smart_query"]').value = query;
}
// Use smart query (for buttons)
function useSmartQuery(query) {
document.querySelector('input[name="smart_query"]').value = query;
document.querySelector("form").submit();
}
// Toggle query details
function toggleQueryDetails() {
const details = document.getElementById("queryDetails");
details.classList.toggle("hidden");
}
// Export results
function exportResults() {
const query = encodeURIComponent(
document.querySelector('input[name="smart_query"]').value
);
window.location.href = `/smart-reports/export?smart_query=${query}`;
}
// Clear query
function clearQuery() {
window.location.href = "/smart-reports";
}
// Format column names for display
function formatColumnName(name) {
return name.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
}
// Auto-complete functionality
document.addEventListener("DOMContentLoaded", function () {
const input = document.querySelector('input[name="smart_query"]');
const suggestions = [
"volunteers who went to",
"poll responses for address",
"donations by volunteer",
"team with most appointments",
"people in team",
"money made by team",
"visited addresses",
"active polls",
"appointments for",
"users with role",
"polls with donations over",
"addresses visited by",
"team leads with more than",
"donations per address",
"unvisited addresses with polls",
];
// Simple autocomplete
input.addEventListener("input", function () {
const value = this.value.toLowerCase();
// You could implement autocomplete dropdown here
console.log("Typing:", value);
});
// Submit on Enter
input.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
e.preventDefault();
this.form.submit();
}
});
// Focus on load
input.focus();
});
// Real-time query validation
function validateQuery(query) {
const keywords = [
"volunteer",
"user",
"poll",
"address",
"appointment",
"team",
"donation",
];
const hasKeyword = keywords.some((keyword) =>
query.toLowerCase().includes(keyword)
);
if (!hasKeyword) {
return {
valid: false,
message:
"Query should include at least one entity (volunteer, poll, address, etc.)",
};
}
return { valid: true };
}
// Query suggestions based on context
function getContextualSuggestions(partialQuery) {
const suggestions = [];
const query = partialQuery.toLowerCase();
if (query.includes("volunteer")) {
suggestions.push("volunteers who went to Main Street");
suggestions.push("volunteers with most donations");
suggestions.push("volunteer activity by month");
}
if (query.includes("team")) {
suggestions.push("team with most appointments");
suggestions.push("people in team 1");
suggestions.push("money made by team 2");
}
if (query.includes("address")) {
suggestions.push("addresses visited by volunteer John");
suggestions.push("poll responses for 123 Oak Street");
suggestions.push("unvisited addresses with polls");
}
return suggestions;
}
// Keyboard shortcuts
document.addEventListener("keydown", function (e) {
// Ctrl/Cmd + Enter to submit
if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
document.querySelector("form").submit();
}
// Escape to clear
if (e.key === "Escape") {
clearQuery();
}
});
</script>
<style>
/* Enhanced styling for smart interface */
.smart-query-input:focus {
box-shadow: 0 0 0 3px rgba(147, 51, 234, 0.1);
border-color: #9333ea;
}
/* Syntax highlighting for code examples */
code {
background-color: #f3f4f6;
padding: 2px 4px;
border-radius: 3px;
font-family: "Courier New", monospace;
font-size: 0.875em;
}
/* Hover effects for query buttons */
.query-button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* Loading animation */
.loading {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
/* Results table styling */
.results-table th {
position: sticky;
top: 0;
background: white;
z-index: 10;
}
/* Print styles */
@media print {
.no-print {
display: none !important;
}
body {
background: white !important;
}
.border-gray-200 {
border-color: #000 !important;
}
}
/* Mobile responsive adjustments */
@media (max-width: 768px) {
.px-8 {
padding-left: 1rem;
padding-right: 1rem;
}
.overflow-x-auto {
-webkit-overflow-scrolling: touch;
}
}
</style>
</body>
</html>
{{ end }}

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"github.com/joho/godotenv"
)
@@ -45,6 +46,17 @@ var templateFuncs = template.FuncMap{
}
return pages
},
"formatColumnName": func(name string) string {
// Replace underscores with spaces and title case each word
formatted := strings.ReplaceAll(name, "_", " ")
words := strings.Fields(formatted)
for i, word := range words {
if len(word) > 0 {
words[i] = strings.ToUpper(string(word[0])) + strings.ToLower(word[1:])
}
}
return strings.Join(words, " ")
},
}
func Render(w http.ResponseWriter, tmpl string, data interface{}) {