1224 lines
31 KiB
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> |