Update: Few issues to resolve see readme
this push will conclude the majority of pulls. this repos will now, not be actively be managed or any further code pushes will not be frequent.
This commit is contained in:
@@ -1,285 +1,681 @@
|
||||
{{ 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"
|
||||
/>
|
||||
<!-- Toolbar -->
|
||||
<div class="bg-white border-b border-gray-200 px-4 md:px-6 py-4">
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4"
|
||||
>
|
||||
<!-- Search & Filters -->
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-start sm:items-center gap-4 w-full sm:w-auto"
|
||||
>
|
||||
<!-- Search -->
|
||||
<div class="relative w-full sm:w-auto">
|
||||
<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-full sm:w-80 pl-10 pr-4 py-2 text-sm border border-gray-200 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Role Filter -->
|
||||
<div class="flex items-center gap-2">
|
||||
<label
|
||||
for="roleFilter"
|
||||
class="text-sm text-gray-600 whitespace-nowrap"
|
||||
>Role:</label
|
||||
>
|
||||
<select
|
||||
x-model="roleFilter"
|
||||
class="px-3 py-2 text-sm border border-gray-200 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<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 rounded-lg transition-colors"
|
||||
>
|
||||
<i class="fas fa-times mr-1"></i>Clear
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Actions & Results -->
|
||||
<div
|
||||
class="flex flex-col sm:flex-row items-start sm:items-center gap-4 w-full sm:w-auto"
|
||||
>
|
||||
<button
|
||||
class="px-6 py-2.5 bg-green-600 text-white text-sm font-medium hover:bg-green-700 transition-colors rounded-lg"
|
||||
onclick="openAddVolunteerPanel()"
|
||||
>
|
||||
<i class="fas fa-user-plus mr-2"></i>Add Volunteer
|
||||
</button>
|
||||
|
||||
<!-- Results Count -->
|
||||
<div class="text-sm text-gray-600 whitespace-nowrap">
|
||||
Showing <span x-text="filteredVolunteers.length"></span> of
|
||||
<span x-text="volunteers.length"></span> volunteers
|
||||
</div>
|
||||
</div>
|
||||
</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"
|
||||
<!-- Table Container -->
|
||||
<div class="flex-1 p-4 md:p-6 overflow-auto">
|
||||
<div
|
||||
class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden"
|
||||
>
|
||||
<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>
|
||||
<!-- Desktop Table -->
|
||||
<div class="hidden lg:block overflow-x-auto">
|
||||
<table class="w-full min-w-full">
|
||||
<thead class="bg-gray-50 border-b border-gray-200">
|
||||
<tr>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider 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 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-2 cursor-pointer"
|
||||
@click="sortBy('FirstName')"
|
||||
>
|
||||
Name
|
||||
<i
|
||||
class="fas"
|
||||
:class="getSortIcon('FirstName')"
|
||||
></i>
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
<div
|
||||
class="flex items-center gap-2 cursor-pointer"
|
||||
@click="sortBy('Email')"
|
||||
>
|
||||
Contact
|
||||
<i
|
||||
class="fas"
|
||||
:class="getSortIcon('Email')"
|
||||
></i>
|
||||
</div>
|
||||
</th>
|
||||
<th
|
||||
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider 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 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-100">
|
||||
<template
|
||||
x-for="volunteer in filteredVolunteers"
|
||||
:key="volunteer.UserID"
|
||||
>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4">
|
||||
<div
|
||||
class="text-sm font-medium text-gray-900"
|
||||
x-text="volunteer.UserID"
|
||||
></div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div
|
||||
class="text-sm font-medium text-gray-900"
|
||||
x-text="volunteer.FirstName + ' ' + volunteer.LastName"
|
||||
></div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div
|
||||
class="text-sm text-gray-900"
|
||||
x-text="volunteer.Email"
|
||||
></div>
|
||||
<div
|
||||
class="text-sm text-gray-500"
|
||||
x-text="volunteer.Phone"
|
||||
></div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span
|
||||
class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full"
|
||||
:class="getRoleBadgeClass(volunteer.RoleID)"
|
||||
x-text="getRoleName(volunteer.RoleID)"
|
||||
></span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button
|
||||
class="text-blue-600 hover:text-blue-800 p-1"
|
||||
@click="editVolunteer(volunteer)"
|
||||
title="Edit volunteer"
|
||||
>
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button
|
||||
class="text-red-600 hover:text-red-800 p-1"
|
||||
@click="deleteVolunteer(volunteer.UserID)"
|
||||
title="Delete volunteer"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 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
|
||||
<!-- No Results - Desktop -->
|
||||
<div
|
||||
x-show="filteredVolunteers.length === 0"
|
||||
class="px-6 py-8 text-center text-gray-500"
|
||||
>
|
||||
<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 volunteers found
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
<!-- Mobile Cards -->
|
||||
<div class="lg:hidden">
|
||||
<div class="space-y-4 p-4">
|
||||
<template
|
||||
x-for="volunteer in filteredVolunteers"
|
||||
:key="volunteer.UserID"
|
||||
>
|
||||
<div
|
||||
class="bg-white border border-gray-200 rounded-lg shadow-sm overflow-hidden"
|
||||
>
|
||||
<!-- Card Header -->
|
||||
<div
|
||||
class="bg-gray-50 px-4 py-3 border-b border-gray-200 flex items-center justify-between"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fas fa-user text-gray-400"></i>
|
||||
<span
|
||||
class="text-sm font-semibold text-gray-900"
|
||||
>Volunteer #<span
|
||||
x-text="volunteer.UserID"
|
||||
></span
|
||||
></span>
|
||||
</div>
|
||||
<span
|
||||
class="inline-flex items-center px-2 py-1 text-xs font-medium rounded-full"
|
||||
:class="getRoleBadgeClass(volunteer.RoleID)"
|
||||
x-text="getRoleName(volunteer.RoleID)"
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<!-- Card Content -->
|
||||
<div class="p-4 space-y-3">
|
||||
<!-- Name -->
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
class="text-sm font-medium text-gray-900"
|
||||
x-text="volunteer.FirstName + ' ' + volunteer.LastName"
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-gray-500"
|
||||
>Email</span
|
||||
>
|
||||
<span
|
||||
class="text-sm text-gray-900"
|
||||
x-text="volunteer.Email"
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-gray-500"
|
||||
>Phone</span
|
||||
>
|
||||
<span
|
||||
class="text-sm text-gray-900"
|
||||
x-text="volunteer.Phone"
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div
|
||||
class="flex justify-center space-x-4 pt-3 border-t border-gray-100"
|
||||
>
|
||||
<button
|
||||
class="flex-1 px-4 py-2 bg-blue-500 text-white text-sm rounded-md hover:bg-blue-600 transition-colors font-medium"
|
||||
@click="editVolunteer(volunteer)"
|
||||
>
|
||||
<i class="fas fa-edit mr-1"></i> Edit
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 text-red-600 hover:text-red-800 hover:bg-red-50 rounded-md transition-colors text-sm font-medium"
|
||||
@click="deleteVolunteer(volunteer.UserID)"
|
||||
>
|
||||
<i class="fas fa-trash mr-1"></i> Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- No Results - Mobile -->
|
||||
<div
|
||||
x-show="filteredVolunteers.length === 0"
|
||||
class="text-center py-12"
|
||||
>
|
||||
<div class="text-gray-400 mb-4">
|
||||
<i class="fas fa-users text-4xl"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">
|
||||
No volunteers found
|
||||
</h3>
|
||||
<p class="text-gray-500">
|
||||
Try adjusting your search or filter criteria.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panel Overlay -->
|
||||
<div
|
||||
id="volunteerPanelOverlay"
|
||||
class="fixed inset-0 bg-black bg-opacity-50 hidden z-40"
|
||||
></div>
|
||||
|
||||
<!-- Add/Edit Volunteer Panel -->
|
||||
<div
|
||||
id="volunteerPanel"
|
||||
class="fixed top-0 right-0 h-full w-full max-w-md bg-white shadow-xl transform translate-x-full transition-transform duration-300 ease-in-out z-50 flex flex-col"
|
||||
>
|
||||
<!-- Panel Header -->
|
||||
<div
|
||||
class="flex justify-between items-center px-6 py-4 border-b border-gray-200 bg-gray-50"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fas fa-user-plus text-blue-500" id="panelIcon"></i>
|
||||
<h2 class="text-lg font-semibold text-gray-900" id="panelTitle">
|
||||
Add Volunteer
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
onclick="closeVolunteerPanel()"
|
||||
class="text-gray-400 hover:text-gray-600 focus:outline-none p-1"
|
||||
>
|
||||
<i class="fas fa-times text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Panel Body -->
|
||||
<form
|
||||
id="volunteerForm"
|
||||
method="POST"
|
||||
action="/volunteer/add"
|
||||
class="flex-1 overflow-y-auto p-6 space-y-6"
|
||||
>
|
||||
<input type="hidden" name="id" id="volunteerId" />
|
||||
|
||||
<!-- First Name -->
|
||||
<div>
|
||||
<label
|
||||
for="firstName"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
<i class="fas fa-user mr-2 text-gray-400"></i>First Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="firstName"
|
||||
name="first_name"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-white text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Last Name -->
|
||||
<div>
|
||||
<label
|
||||
for="lastName"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
<i class="fas fa-user mr-2 text-gray-400"></i>Last Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="lastName"
|
||||
name="last_name"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-white text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div>
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
<i class="fas fa-envelope mr-2 text-gray-400"></i>Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-white text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div>
|
||||
<label
|
||||
for="phone"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
<i class="fas fa-phone mr-2 text-gray-400"></i>Phone
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
name="phone"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-white text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Role Selection -->
|
||||
<div>
|
||||
<label
|
||||
for="role"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>
|
||||
<i class="fas fa-user-tag mr-2 text-gray-400"></i>Role
|
||||
</label>
|
||||
<select
|
||||
name="role_id"
|
||||
id="role"
|
||||
required
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-white text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="">-- Select Role --</option>
|
||||
<option value="1">Admin</option>
|
||||
<option value="2">Team Leader</option>
|
||||
<option value="3">Volunteer</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Panel Footer -->
|
||||
<div
|
||||
class="flex justify-end gap-3 px-6 py-4 border-t border-gray-200 bg-gray-50"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick="closeVolunteerPanel()"
|
||||
class="px-6 py-2 border border-gray-300 text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded-lg font-medium transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
form="volunteerForm"
|
||||
id="submitBtn"
|
||||
class="px-6 py-2 bg-blue-500 text-white hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded-lg font-medium transition-colors"
|
||||
>
|
||||
<i class="fas fa-check mr-2"></i>
|
||||
<span id="submitText">Add Volunteer</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
|
||||
defer
|
||||
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 }}
|
||||
],
|
||||
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);
|
||||
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;
|
||||
// Role filter
|
||||
const roleMatch = !this.roleFilter || volunteer.RoleID.toString() === this.roleFilter;
|
||||
|
||||
return searchMatch && roleMatch;
|
||||
});
|
||||
return searchMatch && roleMatch;
|
||||
});
|
||||
|
||||
// Sort filtered results
|
||||
if (this.sortColumn) {
|
||||
filtered.sort((a, b) => {
|
||||
let aValue = a[this.sortColumn];
|
||||
let bValue = b[this.sortColumn];
|
||||
// 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();
|
||||
}
|
||||
// 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;
|
||||
let comparison = 0;
|
||||
if (aValue > bValue) comparison = 1;
|
||||
if (aValue < bValue) comparison = -1;
|
||||
|
||||
return this.sortDirection === 'asc' ? comparison : -comparison;
|
||||
});
|
||||
}
|
||||
return this.sortDirection === 'asc' ? comparison : -comparison;
|
||||
});
|
||||
}
|
||||
|
||||
return filtered;
|
||||
},
|
||||
return filtered;
|
||||
},
|
||||
|
||||
sortBy(column) {
|
||||
if (this.sortColumn === column) {
|
||||
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
this.sortColumn = column;
|
||||
this.sortDirection = 'asc';
|
||||
}
|
||||
},
|
||||
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';
|
||||
},
|
||||
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';
|
||||
}
|
||||
},
|
||||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
getRoleBadgeClass(roleId) {
|
||||
switch (roleId) {
|
||||
case 1: return 'bg-red-100 text-red-700'; // Admin - Red
|
||||
case 2: return 'bg-blue-100 text-blue-700'; // Team Leader - Blue
|
||||
case 3: return 'bg-green-100 text-green-700'; // Volunteer - Green
|
||||
default: return 'bg-gray-100 text-gray-700';
|
||||
}
|
||||
},
|
||||
|
||||
clearFilters() {
|
||||
this.searchTerm = '';
|
||||
this.roleFilter = '';
|
||||
this.sortColumn = '';
|
||||
this.sortDirection = 'asc';
|
||||
},
|
||||
|
||||
editVolunteer(volunteer) {
|
||||
openEditVolunteerPanel(volunteer);
|
||||
},
|
||||
|
||||
deleteVolunteer(volunteerId) {
|
||||
if (confirm('Are you sure you want to delete this volunteer?')) {
|
||||
// Create and submit form for deletion
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/volunteer/delete';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'id';
|
||||
input.value = volunteerId;
|
||||
|
||||
form.appendChild(input);
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Panel functions
|
||||
function openAddVolunteerPanel() {
|
||||
document.getElementById('panelTitle').textContent = 'Add Volunteer';
|
||||
document.getElementById('panelIcon').className = 'fas fa-user-plus text-blue-500';
|
||||
document.getElementById('submitText').textContent = 'Add Volunteer';
|
||||
document.getElementById('volunteerForm').action = '/volunteer/add';
|
||||
document.getElementById('volunteerForm').reset();
|
||||
document.getElementById('volunteerId').value = '';
|
||||
|
||||
showPanel();
|
||||
}
|
||||
|
||||
function openEditVolunteerPanel(volunteer) {
|
||||
document.getElementById('panelTitle').textContent = 'Edit Volunteer';
|
||||
document.getElementById('panelIcon').className = 'fas fa-user-edit text-blue-500';
|
||||
document.getElementById('submitText').textContent = 'Update Volunteer';
|
||||
document.getElementById('volunteerForm').action = '/volunteer/edit';
|
||||
|
||||
// Populate form
|
||||
document.getElementById('volunteerId').value = volunteer.UserID;
|
||||
document.getElementById('firstName').value = volunteer.FirstName;
|
||||
document.getElementById('lastName').value = volunteer.LastName;
|
||||
document.getElementById('email').value = volunteer.Email;
|
||||
document.getElementById('phone').value = volunteer.Phone;
|
||||
document.getElementById('role').value = volunteer.RoleID;
|
||||
|
||||
showPanel();
|
||||
}
|
||||
|
||||
function showPanel() {
|
||||
document.getElementById('volunteerPanelOverlay').classList.remove('hidden');
|
||||
document.getElementById('volunteerPanel').classList.remove('translate-x-full');
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('firstName').focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function closeVolunteerPanel() {
|
||||
document.getElementById('volunteerPanel').classList.add('translate-x-full');
|
||||
document.getElementById('volunteerPanelOverlay').classList.add('hidden');
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('volunteerForm').reset();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Close panel when clicking overlay
|
||||
document.getElementById('volunteerPanelOverlay').addEventListener('click', closeVolunteerPanel);
|
||||
|
||||
// Close panel on Escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
const overlay = document.getElementById('volunteerPanelOverlay');
|
||||
if (!overlay.classList.contains('hidden')) {
|
||||
closeVolunteerPanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Consistent styling */
|
||||
input,
|
||||
select,
|
||||
button {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button {
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
/* Mobile responsive adjustments */
|
||||
@media (max-width: 640px) {
|
||||
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
{{ end }}
|
||||
|
||||
Reference in New Issue
Block a user