changing some things for reports section
This commit is contained in:
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
752
app/internal/templates/smart_reports.html
Normal file
752
app/internal/templates/smart_reports.html
Normal 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 }}
|
||||
Reference in New Issue
Block a user