monacousa-portal/pages/admin/members/index.vue

1224 lines
31 KiB
Vue

<template>
<div class="bolt-members-page">
<!-- Animated Background -->
<div class="bg-gradient-animated"></div>
<div class="bg-mesh-overlay"></div>
<!-- Floating Orbs -->
<div class="floating-orb orb-1"></div>
<div class="floating-orb orb-2"></div>
<div class="floating-orb orb-3"></div>
<div class="members-wrapper">
<!-- Ultra-Modern Header -->
<div class="members-header">
<div class="header-glass-card">
<div class="header-content">
<div class="header-left">
<div class="icon-wrapper">
<div class="icon-glass">
<v-icon size="32" color="white">mdi-account-group</v-icon>
</div>
<div class="icon-badge">
<v-icon size="14" color="white">mdi-sparkles</v-icon>
</div>
</div>
<div class="title-section">
<h1 class="page-title">
Members <span class="gradient-text">Directory</span>
</h1>
<p class="subtitle">{{ filteredMembers.length }} members found</p>
</div>
</div>
<div class="header-actions">
<button class="btn-glass-export">
<v-icon size="18">mdi-download</v-icon>
<span>Export</span>
</button>
<button class="btn-primary-action">
<v-icon size="18">mdi-plus</v-icon>
<span>Add Member</span>
</button>
</div>
</div>
</div>
</div>
<!-- Stats Row -->
<div class="stats-row">
<div class="stat-card stat-total">
<div class="stat-inner">
<div class="stat-icon">
<v-icon size="24">mdi-account-multiple</v-icon>
</div>
<div class="stat-content">
<div class="stat-value">1,247</div>
<div class="stat-label">Total Members</div>
</div>
<div class="stat-trend trend-up">
<v-icon size="14">mdi-trending-up</v-icon>
<span>+12%</span>
</div>
</div>
</div>
<div class="stat-card stat-active">
<div class="stat-inner">
<div class="stat-icon">
<v-icon size="24">mdi-check-circle</v-icon>
</div>
<div class="stat-content">
<div class="stat-value">1,158</div>
<div class="stat-label">Active</div>
</div>
<div class="stat-badge">93%</div>
</div>
</div>
<div class="stat-card stat-pending">
<div class="stat-inner">
<div class="stat-icon">
<v-icon size="24">mdi-clock-outline</v-icon>
</div>
<div class="stat-content">
<div class="stat-value">45</div>
<div class="stat-label">Pending</div>
</div>
</div>
</div>
<div class="stat-card stat-overdue">
<div class="stat-inner">
<div class="stat-icon">
<v-icon size="24">mdi-alert-circle</v-icon>
</div>
<div class="stat-content">
<div class="stat-value">89</div>
<div class="stat-label">Overdue</div>
</div>
<div class="stat-trend trend-down">
<v-icon size="14">mdi-trending-down</v-icon>
<span>-8%</span>
</div>
</div>
</div>
</div>
<!-- Filters and Search -->
<div class="filters-section">
<div class="filters-glass-card">
<div class="filters-content">
<div class="filter-chips">
<button
v-for="filter in filterOptions"
:key="filter.id"
@click="activeFilter = filter.id"
:class="['filter-chip', { 'active': activeFilter === filter.id }]"
:style="activeFilter === filter.id ? `background: linear-gradient(135deg, ${filter.gradient})` : ''"
>
<span class="chip-label">{{ filter.label }}</span>
<span class="chip-count">{{ filter.count }}</span>
</button>
</div>
<div class="search-section">
<div class="search-input-wrapper">
<v-icon class="search-icon">mdi-magnify</v-icon>
<input
v-model="searchQuery"
type="text"
placeholder="Search members..."
class="search-input"
/>
</div>
<button class="btn-filter-advanced">
<v-icon size="18">mdi-filter-variant</v-icon>
<span>Advanced</span>
</button>
</div>
</div>
</div>
</div>
<!-- Members Table -->
<div class="table-section">
<div class="table-glass-card">
<div class="table-wrapper">
<table class="members-table">
<thead>
<tr>
<th class="th-member">
<button @click="handleSort('name')" class="sort-button">
<span>Member</span>
<v-icon size="16">{{ getSortIcon('name') }}</v-icon>
</button>
</th>
<th class="th-status">
<button @click="handleSort('status')" class="sort-button">
<span>Status</span>
<v-icon size="16">{{ getSortIcon('status') }}</v-icon>
</button>
</th>
<th class="th-joined">
<button @click="handleSort('joinDate')" class="sort-button">
<span>Joined</span>
<v-icon size="16">{{ getSortIcon('joinDate') }}</v-icon>
</button>
</th>
<th class="th-dues">
<button @click="handleSort('duesStatus')" class="sort-button">
<span>Dues Status</span>
<v-icon size="16">{{ getSortIcon('duesStatus') }}</v-icon>
</button>
</th>
<th class="th-actions">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="(member, index) in paginatedMembers" :key="member.id"
class="member-row"
:style="`animation-delay: ${index * 50}ms`">
<td class="td-member">
<div class="member-cell">
<div class="member-avatar">
<div class="avatar-circle" :style="`background: linear-gradient(135deg, ${getAvatarGradient(member.firstName)});`">
{{ getInitials(member.firstName, member.lastName) }}
</div>
<div v-if="member.status === 'Active'" class="avatar-status"></div>
</div>
<div class="member-info">
<div class="member-name">{{ member.firstName }} {{ member.lastName }}</div>
<div class="member-email">{{ member.email }}</div>
</div>
</div>
</td>
<td class="td-status">
<span :class="['status-badge', `status-${member.status.toLowerCase()}`]">
<span class="status-dot"></span>
{{ member.status }}
</span>
</td>
<td class="td-joined">
<div class="date-text">{{ formatDate(member.joinDate) }}</div>
</td>
<td class="td-dues">
<span :class="['dues-badge', `dues-${member.duesStatus.toLowerCase().replace(' ', '-')}`]">
<v-icon size="14">{{ getDuesIcon(member.duesStatus) }}</v-icon>
{{ member.duesStatus }}
</span>
</td>
<td class="td-actions">
<div class="action-buttons">
<button class="action-btn" title="View">
<v-icon size="18">mdi-eye</v-icon>
</button>
<button class="action-btn" title="Edit">
<v-icon size="18">mdi-pencil</v-icon>
</button>
<button class="action-btn" title="Email">
<v-icon size="18">mdi-email</v-icon>
</button>
<button class="action-btn action-btn-danger" title="Delete">
<v-icon size="18">mdi-delete</v-icon>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="pagination-section">
<div class="pagination-info">
Showing {{ (currentPage - 1) * itemsPerPage + 1 }} to {{ Math.min(currentPage * itemsPerPage, filteredMembers.length) }} of {{ filteredMembers.length }} members
</div>
<div class="pagination-controls">
<button
@click="currentPage--"
:disabled="currentPage === 1"
class="pagination-btn"
>
<v-icon size="20">mdi-chevron-left</v-icon>
</button>
<button
v-for="page in displayedPages"
:key="page"
@click="currentPage = page"
:class="['pagination-number', { 'active': currentPage === page }]"
>
{{ page }}
</button>
<button
@click="currentPage++"
:disabled="currentPage === totalPages"
class="pagination-btn"
>
<v-icon size="20">mdi-chevron-right</v-icon>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
// Remove authentication middleware
definePageMeta({
layout: 'admin',
// middleware: 'admin' // Removed for direct access
});
// Mock data for demonstration
const members = ref([
{ id: 'M001', firstName: 'John', lastName: 'Anderson', email: 'john.anderson@monacousa.org', status: 'Active', joinDate: '2023-01-15', duesStatus: 'Paid' },
{ id: 'M002', firstName: 'Sarah', lastName: 'Mitchell', email: 'sarah.mitchell@monacousa.org', status: 'Active', joinDate: '2023-02-20', duesStatus: 'Paid' },
{ id: 'M003', firstName: 'Michael', lastName: 'Thompson', email: 'michael.thompson@monacousa.org', status: 'Active', joinDate: '2023-03-10', duesStatus: 'Due Soon' },
{ id: 'M004', firstName: 'Emma', lastName: 'Williams', email: 'emma.williams@monacousa.org', status: 'Inactive', joinDate: '2022-11-05', duesStatus: 'Overdue' },
{ id: 'M005', firstName: 'David', lastName: 'Brown', email: 'david.brown@monacousa.org', status: 'Active', joinDate: '2023-04-18', duesStatus: 'Paid' },
{ id: 'M006', firstName: 'Lisa', lastName: 'Davis', email: 'lisa.davis@monacousa.org', status: 'Active', joinDate: '2023-05-22', duesStatus: 'Paid' },
{ id: 'M007', firstName: 'Robert', lastName: 'Garcia', email: 'robert.garcia@monacousa.org', status: 'Pending', joinDate: '2023-06-30', duesStatus: 'Pending' },
{ id: 'M008', firstName: 'Jennifer', lastName: 'Martinez', email: 'jennifer.martinez@monacousa.org', status: 'Active', joinDate: '2023-07-14', duesStatus: 'Due Soon' },
{ id: 'M009', firstName: 'William', lastName: 'Rodriguez', email: 'william.rodriguez@monacousa.org', status: 'Active', joinDate: '2023-08-09', duesStatus: 'Paid' },
{ id: 'M010', firstName: 'Patricia', lastName: 'Lee', email: 'patricia.lee@monacousa.org', status: 'Active', joinDate: '2023-09-03', duesStatus: 'Paid' },
{ id: 'M011', firstName: 'James', lastName: 'Walker', email: 'james.walker@monacousa.org', status: 'Active', joinDate: '2023-10-12', duesStatus: 'Paid' },
{ id: 'M012', firstName: 'Mary', lastName: 'Hall', email: 'mary.hall@monacousa.org', status: 'Inactive', joinDate: '2022-12-20', duesStatus: 'Overdue' },
]);
const searchQuery = ref('');
const activeFilter = ref('all');
const sortField = ref('name');
const sortDirection = ref('asc');
const currentPage = ref(1);
const itemsPerPage = 10;
const filterOptions = [
{ id: 'all', label: 'All Members', count: members.value.length, gradient: '#6B7280, #4B5563' },
{ id: 'active', label: 'Active', count: members.value.filter(m => m.status === 'Active').length, gradient: '#10B981, #059669' },
{ id: 'inactive', label: 'Inactive', count: members.value.filter(m => m.status === 'Inactive').length, gradient: '#9CA3AF, #6B7280' },
{ id: 'pending', label: 'Pending', count: members.value.filter(m => m.status === 'Pending').length, gradient: '#F59E0B, #D97706' },
{ id: 'overdue', label: 'Overdue', count: members.value.filter(m => m.duesStatus === 'Overdue').length, gradient: '#EF4444, #DC2626' },
];
const filteredMembers = computed(() => {
return members.value.filter(member => {
const matchesSearch =
`${member.firstName} ${member.lastName}`.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
member.email.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
member.id.toLowerCase().includes(searchQuery.value.toLowerCase());
const matchesFilter =
activeFilter.value === 'all' ||
(activeFilter.value === 'active' && member.status === 'Active') ||
(activeFilter.value === 'inactive' && member.status === 'Inactive') ||
(activeFilter.value === 'pending' && member.status === 'Pending') ||
(activeFilter.value === 'overdue' && member.duesStatus === 'Overdue');
return matchesSearch && matchesFilter;
});
});
const sortedMembers = computed(() => {
const sorted = [...filteredMembers.value].sort((a, b) => {
let aValue: any;
let bValue: any;
switch (sortField.value) {
case 'name':
aValue = `${a.firstName} ${a.lastName}`;
bValue = `${b.firstName} ${b.lastName}`;
break;
case 'status':
aValue = a.status;
bValue = b.status;
break;
case 'joinDate':
aValue = new Date(a.joinDate).getTime();
bValue = new Date(b.joinDate).getTime();
break;
case 'duesStatus':
aValue = a.duesStatus;
bValue = b.duesStatus;
break;
default:
aValue = '';
bValue = '';
}
if (sortDirection.value === 'asc') {
return aValue < bValue ? -1 : aValue > bValue ? 1 : 0;
} else {
return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
}
});
return sorted;
});
const paginatedMembers = computed(() => {
return sortedMembers.value.slice(
(currentPage.value - 1) * itemsPerPage,
currentPage.value * itemsPerPage
);
});
const totalPages = computed(() => Math.ceil(filteredMembers.value.length / itemsPerPage));
const displayedPages = computed(() => {
const pages = [];
const start = Math.max(1, currentPage.value - 2);
const end = Math.min(totalPages.value, start + 4);
for (let i = start; i <= end; i++) {
pages.push(i);
}
return pages;
});
const handleSort = (field: string) => {
if (sortField.value === field) {
sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc';
} else {
sortField.value = field;
sortDirection.value = 'asc';
}
};
const getSortIcon = (field: string) => {
if (sortField.value !== field) return 'mdi-unfold-more-horizontal';
return sortDirection.value === 'asc' ? 'mdi-chevron-up' : 'mdi-chevron-down';
};
const getInitials = (firstName: string, lastName: string) => {
return `${firstName[0]}${lastName[0]}`.toUpperCase();
};
const getAvatarGradient = (name: string) => {
const gradients = [
'#667EEA, #764BA2',
'#F093FB, #F5576C',
'#4FACFE, #00F2FE',
'#43E97B, #38F9D7',
'#FA709A, #FEE140',
'#30CFD0, #330867',
];
return gradients[name.charCodeAt(0) % gradients.length];
};
const formatDate = (date: string) => {
return new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
};
const getDuesIcon = (status: string) => {
switch (status) {
case 'Paid': return 'mdi-check-circle';
case 'Due Soon': return 'mdi-clock-alert-outline';
case 'Overdue': return 'mdi-alert-circle';
case 'Pending': return 'mdi-clock-outline';
default: return 'mdi-circle-outline';
}
};
</script>
<style scoped lang="scss">
.bolt-members-page {
position: relative;
min-height: 100vh;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
overflow-x: hidden;
}
// Remove animated backgrounds - using subtle glass design
.bg-gradient-animated,
.bg-mesh-overlay,
.floating-orb {
display: none;
}
@keyframes gradientShift {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.bg-mesh-overlay {
position: absolute;
inset: 0;
background-image:
repeating-linear-gradient(45deg, transparent, transparent 35px, rgba(255, 255, 255, 0.01) 35px, rgba(255, 255, 255, 0.01) 70px),
repeating-linear-gradient(-45deg, transparent, transparent 35px, rgba(255, 255, 255, 0.01) 35px, rgba(255, 255, 255, 0.01) 70px);
}
// Floating Orbs
.floating-orb {
position: absolute;
border-radius: 50%;
filter: blur(100px);
animation: float 20s ease-in-out infinite;
pointer-events: none;
}
.orb-1 {
width: 300px;
height: 300px;
background: radial-gradient(circle, rgba(239, 68, 68, 0.15), transparent);
top: 10%;
right: 10%;
animation-delay: 0s;
}
.orb-2 {
width: 200px;
height: 200px;
background: radial-gradient(circle, rgba(59, 130, 246, 0.15), transparent);
bottom: 20%;
left: 5%;
animation-delay: 5s;
}
.orb-3 {
width: 250px;
height: 250px;
background: radial-gradient(circle, rgba(168, 85, 247, 0.15), transparent);
top: 50%;
left: 50%;
animation-delay: 10s;
}
@keyframes float {
0%, 100% { transform: translate(0, 0) scale(1); }
33% { transform: translate(30px, -30px) scale(1.1); }
66% { transform: translate(-20px, 20px) scale(0.9); }
}
.members-wrapper {
position: relative;
padding: 2rem;
max-width: 1400px;
margin: 0 auto;
}
// Header Section
.members-header {
margin-bottom: 2rem;
animation: slideDown 0.6s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.header-glass-card {
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border-radius: 16px;
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
padding: 2rem;
transition: all 0.3s ease;
}
@keyframes shimmer {
100% { left: 100%; }
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
z-index: 1;
}
.header-left {
display: flex;
align-items: center;
gap: 1.5rem;
}
.icon-wrapper {
position: relative;
}
.icon-glass {
width: 56px;
height: 56px;
background: rgba(220, 38, 38, 0.08);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(220, 38, 38, 0.1);
.v-icon {
color: #dc2626 !important;
}
}
.icon-badge {
position: absolute;
top: -4px;
right: -4px;
width: 24px;
height: 24px;
background: linear-gradient(135deg, #fbbf24, #f59e0b);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(251, 191, 36, 0.4);
}
.page-title {
font-size: 2rem;
font-weight: 700;
color: rgba(0, 0, 0, 0.87);
margin: 0;
letter-spacing: -0.01em;
}
.gradient-text {
color: #dc2626;
}
.subtitle {
color: rgba(0, 0, 0, 0.6);
font-size: 0.875rem;
margin-top: 0.25rem;
}
.header-actions {
display: flex;
gap: 1rem;
}
.btn-glass-export,
.btn-primary-action {
padding: 0.75rem 1.5rem;
border-radius: 12px;
font-weight: 600;
font-size: 0.95rem;
border: none;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.3s ease;
}
.btn-glass-export {
background: rgba(255, 255, 255, 0.15);
color: white;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
&:hover {
background: rgba(255, 255, 255, 0.25);
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}
}
.btn-primary-action {
background: white;
color: #dc2626;
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.2);
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(255, 255, 255, 0.3);
}
}
// Stats Row
.stats-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 20px;
border: 1px solid rgba(239, 68, 68, 0.1);
padding: 1.5rem;
animation: slideUp 0.6s ease-out;
animation-fill-mode: both;
position: relative;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
&::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, transparent, rgba(239, 68, 68, 0.03));
opacity: 0;
transition: opacity 0.3s ease;
}
&:hover::before {
opacity: 1;
}
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
}
&.stat-total { animation-delay: 0.1s; }
&.stat-active { animation-delay: 0.2s; }
&.stat-pending { animation-delay: 0.3s; }
&.stat-overdue { animation-delay: 0.4s; }
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.stat-inner {
display: flex;
align-items: center;
gap: 1rem;
position: relative;
}
.stat-icon {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.stat-total .stat-icon {
background: rgba(107, 114, 128, 0.1);
color: #6b7280;
}
.stat-active .stat-icon {
background: rgba(16, 185, 129, 0.1);
color: #10b981;
}
.stat-pending .stat-icon {
background: rgba(245, 158, 11, 0.1);
color: #f59e0b;
}
.stat-overdue .stat-icon {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
}
.stat-content {
flex: 1;
}
.stat-value {
font-size: 2rem;
font-weight: 800;
color: #111827;
line-height: 1;
}
.stat-label {
color: #6b7280;
font-size: 0.9rem;
margin-top: 0.25rem;
}
.stat-trend {
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
&.trend-up {
background: rgba(16, 185, 129, 0.2);
color: #10b981;
}
&.trend-down {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
}
.stat-badge {
position: absolute;
top: -0.5rem;
right: -0.5rem;
background: linear-gradient(135deg, #10b981, #059669);
color: white;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 700;
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}
// Filters Section
.filters-section {
margin-bottom: 2rem;
animation: slideUp 0.6s ease-out 0.3s;
animation-fill-mode: both;
}
.filters-glass-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 20px;
border: 1px solid rgba(239, 68, 68, 0.1);
padding: 2rem;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
}
.filters-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1.5rem;
}
.filter-chips {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
}
.filter-chip {
padding: 0.625rem 1.25rem;
border-radius: 12px;
border: 2px solid rgba(239, 68, 68, 0.2);
background: white;
color: #374151;
font-weight: 600;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.5rem;
position: relative;
overflow: hidden;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
&.active {
color: white;
border-color: transparent;
transform: scale(1.05);
box-shadow: 0 8px 24px rgba(239, 68, 68, 0.3);
&::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
animation: shimmer 2s infinite;
}
}
}
.chip-label {
position: relative;
z-index: 1;
}
.chip-count {
background: rgba(255, 255, 255, 0.2);
padding: 0.125rem 0.5rem;
border-radius: 20px;
font-size: 0.8rem;
position: relative;
z-index: 1;
}
.search-section {
display: flex;
gap: 1rem;
}
.search-input-wrapper {
position: relative;
width: 320px;
}
.search-icon {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
color: #ef4444;
transition: color 0.3s ease;
}
.search-input {
width: 100%;
padding: 0.875rem 1rem 0.875rem 3rem;
border: 2px solid #fecaca;
border-radius: 12px;
background: #fef2f2;
color: #991b1b;
font-size: 0.95rem;
font-weight: 500;
transition: all 0.3s ease;
outline: none;
&::placeholder {
color: #f87171;
}
&:focus {
border-color: #ef4444;
background: white;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
& ~ .search-icon {
color: #dc2626;
}
}
}
.btn-filter-advanced {
padding: 0.875rem 1.25rem;
border-radius: 12px;
border: 2px solid #e5e7eb;
background: white;
color: #6b7280;
font-weight: 600;
font-size: 0.9rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.3s ease;
&:hover {
border-color: #ef4444;
color: #ef4444;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
// Table Section
.table-section {
animation: slideUp 0.6s ease-out 0.5s;
animation-fill-mode: both;
}
.table-glass-card {
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(20px);
border-radius: 24px;
border: 1px solid rgba(239, 68, 68, 0.1);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.table-wrapper {
overflow-x: auto;
}
.members-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
}
thead {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.05), rgba(239, 68, 68, 0.02));
}
th {
padding: 1.25rem 1.5rem;
text-align: left;
font-weight: 700;
color: #374151;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 2px solid rgba(239, 68, 68, 0.1);
}
.sort-button {
display: flex;
align-items: center;
gap: 0.5rem;
background: none;
border: none;
color: inherit;
font: inherit;
cursor: pointer;
transition: color 0.3s ease;
&:hover {
color: #ef4444;
}
}
.member-row {
animation: fadeIn 0.6s ease-out;
animation-fill-mode: both;
transition: background-color 0.3s ease;
&:hover {
background: rgba(239, 68, 68, 0.02);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
td {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid rgba(239, 68, 68, 0.05);
}
.member-cell {
display: flex;
align-items: center;
gap: 1rem;
}
.member-avatar {
position: relative;
}
.avatar-circle {
width: 44px;
height: 44px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
font-size: 0.95rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.avatar-status {
position: absolute;
bottom: -2px;
right: -2px;
width: 12px;
height: 12px;
background: #10b981;
border: 2px solid white;
border-radius: 50%;
}
.member-name {
font-weight: 600;
color: #111827;
font-size: 0.95rem;
}
.member-email {
color: #6b7280;
font-size: 0.875rem;
margin-top: 0.125rem;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.875rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
&.status-active {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
&.status-inactive {
background: rgba(107, 114, 128, 0.1);
color: #4b5563;
}
&.status-pending {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
}
.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
}
.date-text {
color: #6b7280;
font-size: 0.9rem;
}
.dues-badge {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.875rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
&.dues-paid {
background: rgba(16, 185, 129, 0.1);
color: #059669;
}
&.dues-due-soon {
background: rgba(245, 158, 11, 0.1);
color: #d97706;
}
&.dues-overdue {
background: rgba(239, 68, 68, 0.1);
color: #dc2626;
}
&.dues-pending {
background: rgba(107, 114, 128, 0.1);
color: #4b5563;
}
}
.action-buttons {
display: flex;
gap: 0.5rem;
}
.action-btn {
width: 36px;
height: 36px;
border-radius: 8px;
border: 1px solid #e5e7eb;
background: white;
color: #6b7280;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
border-color: #ef4444;
color: #ef4444;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.1);
}
&.action-btn-danger:hover {
background: #ef4444;
color: white;
border-color: #ef4444;
}
}
// Pagination
.pagination-section {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
background: rgba(239, 68, 68, 0.02);
border-top: 1px solid rgba(239, 68, 68, 0.1);
}
.pagination-info {
color: #6b7280;
font-size: 0.9rem;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 0.5rem;
}
.pagination-btn,
.pagination-number {
width: 40px;
height: 40px;
border-radius: 10px;
border: 1px solid #e5e7eb;
background: white;
color: #6b7280;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-weight: 600;
font-size: 0.9rem;
transition: all 0.3s ease;
&:hover:not(:disabled) {
border-color: #ef4444;
color: #ef4444;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.1);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&.active {
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
border-color: transparent;
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
}
}
</style>