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:
Mann Patel
2025-09-11 16:54:30 -06:00
parent 144436bbf3
commit b21e76eed0
19 changed files with 1953 additions and 1442 deletions

View File

@@ -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 }}