Files
Poll-system/app/internal/templates/volunteer.html
2025-09-05 15:39:06 -06:00

286 lines
9.7 KiB
HTML

{{ define "content" }}
<!-- Main Content -->
<div class="flex-1 flex flex-col overflow-hidden" x-data="volunteerTable()">
<!-- Toolbar -->
<div class="bg-gray-50 border-b border-gray-200 px-6 py-3">
<div class="flex items-center gap-4 text-sm">
<!-- Search -->
<div class="flex items-center gap-2">
<div class="relative">
<i
class="fas fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-sm"
></i>
<input
type="text"
x-model="searchTerm"
placeholder="Search volunteers..."
class="w-64 pl-8 pr-3 py-2 text-sm border border-gray-200 bg-white focus:ring-blue-500 focus:border-blue-500 transition-colors"
/>
</div>
</div>
<!-- Role Filter -->
<div class="flex items-center gap-2">
<label for="roleFilter" class="text-gray-600 font-medium">Role:</label>
<select
x-model="roleFilter"
class="px-3 py-2 text-sm border border-gray-200 bg-white focus:ring-blue-500 focus:border-blue-500 transition-colors"
>
<option value="">All Roles</option>
<option value="1">Admin</option>
<option value="2">Team Leader</option>
<option value="3">Volunteer</option>
</select>
</div>
<!-- Clear Filters -->
<button
@click="clearFilters()"
class="px-3 py-2 text-sm text-gray-600 hover:text-gray-800 hover:bg-gray-100 transition-colors"
>
<i class="fas fa-times mr-1"></i>Clear
</button>
<!-- Results Count -->
<div class="ml-auto">
<span class="text-gray-600 text-sm">
Showing <span x-text="filteredVolunteers.length"></span> of
<span x-text="volunteers.length"></span> volunteers
</span>
</div>
</div>
</div>
<!-- Table Wrapper -->
<div
class="flex-1 overflow-x-auto overflow-y-auto bg-white border border-gray-100"
>
<table class="w-full divide-gray-200 text-sm table-auto">
<!-- Table Head -->
<thead class="bg-gray-50 divide-gray-200">
<tr
class="text-left text-gray-700 font-medium border-b border-gray-200"
>
<th class="px-4 py-3 whitespace-nowrap">
<div
class="flex items-center gap-2 cursor-pointer"
@click="sortBy('UserID')"
>
ID <i class="fas" :class="getSortIcon('UserID')"></i>
</div>
</th>
<th class="px-6 py-3 whitespace-nowrap">
<div
class="flex items-center gap-2 cursor-pointer"
@click="sortBy('FirstName')"
>
First Name <i class="fas" :class="getSortIcon('FirstName')"></i>
</div>
</th>
<th class="px-6 py-3 whitespace-nowrap">
<div
class="flex items-center gap-2 cursor-pointer"
@click="sortBy('LastName')"
>
Last Name <i class="fas" :class="getSortIcon('LastName')"></i>
</div>
</th>
<th class="px-6 py-3 whitespace-nowrap">
<div
class="flex items-center gap-2 cursor-pointer"
@click="sortBy('Email')"
>
Email <i class="fas" :class="getSortIcon('Email')"></i>
</div>
</th>
<th class="px-6 py-3 whitespace-nowrap">
<div
class="flex items-center gap-2 cursor-pointer"
@click="sortBy('Phone')"
>
Phone <i class="fas" :class="getSortIcon('Phone')"></i>
</div>
</th>
<th class="px-6 py-3 whitespace-nowrap">
<div
class="flex items-center gap-2 cursor-pointer"
@click="sortBy('RoleID')"
>
Role <i class="fas" :class="getSortIcon('RoleID')"></i>
</div>
</th>
<th class="px-6 py-3 whitespace-nowrap">Actions</th>
</tr>
</thead>
<!-- Table Body -->
<tbody class="divide-y divide-gray-200">
<template
x-for="volunteer in filteredVolunteers"
:key="volunteer.UserID"
>
<tr class="hover:bg-gray-50">
<td
class="px-6 py-3 whitespace-nowrap"
x-text="volunteer.UserID"
></td>
<td
class="px-6 py-3 whitespace-nowrap"
x-text="volunteer.FirstName"
></td>
<td
class="px-6 py-3 whitespace-nowrap"
x-text="volunteer.LastName"
></td>
<td
class="px-6 py-3 whitespace-nowrap"
x-text="volunteer.Email"
></td>
<td
class="px-6 py-3 whitespace-nowrap"
x-text="volunteer.Phone"
></td>
<td class="px-6 py-3 whitespace-nowrap">
<span
class="px-2 py-1 text-xs font-medium bg-gray-100 text-gray-800"
x-text="getRoleName(volunteer.RoleID)"
></span>
</td>
<td class="px-6 py-3 whitespace-nowrap">
<div class="flex items-center gap-2">
<a
:href="`/volunteer/edit?id=${volunteer.UserID}`"
class="text-blue-600 hover:text-blue-800 font-medium text-xs px-2 py-1 hover:bg-blue-50 transition-colors"
>Edit</a
>
<form
action="/volunteer/delete"
method="POST"
class="inline-block"
>
<input type="hidden" name="id" :value="volunteer.UserID" />
<button
type="submit"
class="text-red-600 hover:text-red-800 font-medium text-xs px-2 py-1 hover:bg-red-50 transition-colors"
@click="return confirm('Are you sure you want to delete this volunteer?')"
>
Delete
</button>
</form>
</div>
</td>
</tr>
</template>
</tbody>
</table>
<!-- No Results Message -->
<div x-show="filteredVolunteers.length === 0" class="text-center py-12">
<i class="fas fa-search text-gray-400 text-3xl mb-4"></i>
<p class="text-gray-600 text-lg mb-2">No volunteers found</p>
<p class="text-gray-500 text-sm">
Try adjusting your search or filter criteria
</p>
</div>
</div>
</div>
<script
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
defer
></script>
<script>
function volunteerTable() {
return {
searchTerm: '',
roleFilter: '',
sortColumn: '',
sortDirection: 'asc',
volunteers: [
{{ range .Users }}
{
UserID: {{ .UserID }},
FirstName: "{{ .FirstName }}",
LastName: "{{ .LastName }}",
Email: "{{ .Email }}",
Phone: "{{ .Phone }}",
RoleID: {{ .RoleID }}
},
{{ end }}
],
get filteredVolunteers() {
let filtered = this.volunteers.filter(volunteer => {
// Search filter
const searchMatch = !this.searchTerm ||
volunteer.FirstName.toLowerCase().includes(this.searchTerm.toLowerCase()) ||
volunteer.LastName.toLowerCase().includes(this.searchTerm.toLowerCase()) ||
volunteer.Email.toLowerCase().includes(this.searchTerm.toLowerCase()) ||
volunteer.Phone.includes(this.searchTerm);
// Role filter
const roleMatch = !this.roleFilter || volunteer.RoleID.toString() === this.roleFilter;
return searchMatch && roleMatch;
});
// Sort filtered results
if (this.sortColumn) {
filtered.sort((a, b) => {
let aValue = a[this.sortColumn];
let bValue = b[this.sortColumn];
// Handle string comparison
if (typeof aValue === 'string') {
aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase();
}
let comparison = 0;
if (aValue > bValue) comparison = 1;
if (aValue < bValue) comparison = -1;
return this.sortDirection === 'asc' ? comparison : -comparison;
});
}
return filtered;
},
sortBy(column) {
if (this.sortColumn === column) {
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
} else {
this.sortColumn = column;
this.sortDirection = 'asc';
}
},
getSortIcon(column) {
if (this.sortColumn !== column) {
return 'fa-sort text-gray-400';
}
return this.sortDirection === 'asc' ? 'fa-sort-up text-blue-600' : 'fa-sort-down text-blue-600';
},
getRoleName(roleId) {
switch (roleId) {
case 1: return 'Admin';
case 2: return 'Team Leader';
case 3: return 'Volunteer';
default: return 'Unknown';
}
},
clearFilters() {
this.searchTerm = '';
this.roleFilter = '';
this.sortColumn = '';
this.sortDirection = 'asc';
}
}
}
</script>
{{ end }}