Initial commit
This commit is contained in:
299
app/internal/templates/volunteer/volunteer.html
Normal file
299
app/internal/templates/volunteer/volunteer.html
Normal file
@@ -0,0 +1,299 @@
|
||||
{{ define "content" }}
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden" x-data="volunteerTable()">
|
||||
<!-- Top Navigation -->
|
||||
<div class="bg-white border-b border-gray-200 px-6 py-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<i
|
||||
class="{{if .PageIcon}}{{.PageIcon}}{{else}}fas fa-users{{end}} text-blue-600"
|
||||
></i>
|
||||
<span class="text-sm font-medium">Volunteers</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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 }}
|
||||
Reference in New Issue
Block a user