425 lines
13 KiB
Vue
425 lines
13 KiB
Vue
<template>
|
|
<div class="min-h-screen bg-gradient-light">
|
|
<!-- Glass Sidebar -->
|
|
<GlassSidebar
|
|
:is-open="sidebarOpen"
|
|
:is-mobile="isMobile"
|
|
:user-name="firstName"
|
|
:user-role="userRole"
|
|
:user-avatar="userAvatar"
|
|
@close="sidebarOpen = false"
|
|
@logout="handleLogout"
|
|
/>
|
|
|
|
<!-- Main Content Area -->
|
|
<div :class="['transition-all duration-300', isMobile ? 'ml-0' : 'ml-72']">
|
|
<!-- Glass Navigation Bar -->
|
|
<header class="glass-navbar sticky top-0 z-30 px-6 py-4">
|
|
<div class="flex items-center justify-between">
|
|
<!-- Mobile Menu Toggle -->
|
|
<button
|
|
v-if="isMobile"
|
|
@click="sidebarOpen = !sidebarOpen"
|
|
class="p-2 rounded-lg hover:bg-glass-monaco-soft transition-colors lg:hidden"
|
|
>
|
|
<Menu class="w-6 h-6 text-gray-700" />
|
|
</button>
|
|
|
|
<!-- Page Title -->
|
|
<div class="flex items-center gap-4">
|
|
<h1 class="text-2xl font-bold text-gradient-monaco">Board Dashboard</h1>
|
|
<span class="px-3 py-1 rounded-full bg-glass-monaco-soft text-monaco-600 text-sm font-medium">
|
|
{{ currentDate }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Header Actions -->
|
|
<div class="flex items-center gap-3">
|
|
<!-- Notifications -->
|
|
<button class="relative p-2 rounded-lg hover:bg-glass-monaco-soft transition-colors">
|
|
<Bell class="w-5 h-5 text-gray-700" />
|
|
<span class="absolute top-1 right-1 w-2 h-2 bg-monaco-600 rounded-full"></span>
|
|
</button>
|
|
|
|
<!-- Quick Actions -->
|
|
<button
|
|
@click="showQuickActions = !showQuickActions"
|
|
class="btn-glass-primary flex items-center gap-2"
|
|
>
|
|
<Plus class="w-4 h-4" />
|
|
Quick Action
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Dashboard Content -->
|
|
<main class="p-6 space-y-6">
|
|
<!-- Welcome Section -->
|
|
<div class="glass-ultra rounded-glass p-8 text-center animate-fade-in">
|
|
<h2 class="text-3xl font-bold text-gray-800 mb-2">
|
|
Welcome back, {{ firstName }}!
|
|
</h2>
|
|
<p class="text-gray-600">
|
|
Here's an overview of MonacoUSA's current status and activities.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Statistics Grid -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
<GlassStatCard
|
|
v-for="stat in stats"
|
|
:key="stat.label"
|
|
:icon="stat.icon"
|
|
:label="stat.label"
|
|
:value="stat.value"
|
|
:prefix="stat.prefix"
|
|
:suffix="stat.suffix"
|
|
:change="stat.change"
|
|
:change-type="stat.changeType"
|
|
:icon-color="stat.color"
|
|
:action-label="stat.actionLabel"
|
|
@click="handleStatClick(stat)"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Dues Management Section - LIMITED TO 4 CARDS -->
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-xl font-semibold text-gray-800">Member Dues Overview</h3>
|
|
<div class="flex items-center gap-3">
|
|
<!-- Filter Tabs -->
|
|
<div class="glass rounded-full p-1 flex gap-1">
|
|
<button
|
|
v-for="tab in duesTabs"
|
|
:key="tab.id"
|
|
@click="activeDuesTab = tab.id"
|
|
:class="[
|
|
'px-4 py-2 rounded-full text-sm font-medium transition-all',
|
|
activeDuesTab === tab.id
|
|
? 'bg-gradient-monaco text-white shadow-monaco-sm'
|
|
: 'text-gray-600 hover:text-monaco-600 hover:bg-glass-monaco-soft'
|
|
]"
|
|
>
|
|
{{ tab.label }}
|
|
<span v-if="tab.count" class="ml-1">({{ tab.count }})</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dues Cards Grid - MAX 4 VISIBLE -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
<GlassDuesCard
|
|
v-for="member in visibleDuesMembers"
|
|
:key="member.id"
|
|
:member="member"
|
|
:status="activeDuesTab"
|
|
@mark-paid="handleMarkPaid"
|
|
@send-reminder="handleSendReminder"
|
|
@view-details="handleViewDetails"
|
|
/>
|
|
</div>
|
|
|
|
<!-- View All Button -->
|
|
<div v-if="totalDuesMembers > 4" class="text-center">
|
|
<button
|
|
@click="navigateToFullDuesList"
|
|
class="btn-glass-secondary inline-flex items-center gap-2"
|
|
>
|
|
View All {{ totalDuesMembers }} Members
|
|
<ArrowRight class="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions Grid -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<!-- Upcoming Events Card -->
|
|
<div class="glass-card-bright">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="font-semibold text-gray-800">Upcoming Events</h3>
|
|
<Calendar class="w-5 h-5 text-monaco-600" />
|
|
</div>
|
|
<div v-if="nextEvent" class="space-y-3">
|
|
<div class="p-4 bg-glass-monaco-soft rounded-xl">
|
|
<h4 class="font-medium text-gray-800">{{ nextEvent.title }}</h4>
|
|
<p class="text-sm text-gray-600 mt-1">{{ nextEvent.date }}</p>
|
|
<p class="text-xs text-gray-500 mt-2">{{ nextEvent.attendees }} attendees</p>
|
|
</div>
|
|
<button class="w-full btn-glass text-sm">
|
|
View All Events
|
|
</button>
|
|
</div>
|
|
<div v-else class="text-center py-8 text-gray-500">
|
|
No upcoming events
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Activity Card -->
|
|
<div class="glass-card-bright">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="font-semibold text-gray-800">Recent Activity</h3>
|
|
<Activity class="w-5 h-5 text-monaco-600" />
|
|
</div>
|
|
<div class="space-y-3">
|
|
<div
|
|
v-for="activity in recentActivity"
|
|
:key="activity.id"
|
|
class="flex items-start gap-3 p-3 rounded-lg hover:bg-glass-monaco-soft transition-colors"
|
|
>
|
|
<div class="w-2 h-2 bg-monaco-600 rounded-full mt-1.5"></div>
|
|
<div class="flex-1">
|
|
<p class="text-sm text-gray-700">{{ activity.description }}</p>
|
|
<p class="text-xs text-gray-500 mt-1">{{ activity.time }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Links Card -->
|
|
<div class="glass-card-bright">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="font-semibold text-gray-800">Quick Actions</h3>
|
|
<Zap class="w-5 h-5 text-monaco-600" />
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<button
|
|
v-for="action in quickActions"
|
|
:key="action.label"
|
|
@click="action.handler"
|
|
class="p-3 rounded-xl bg-white/50 hover:bg-glass-monaco-soft
|
|
transition-all hover:scale-105 group text-center"
|
|
>
|
|
<component
|
|
:is="action.icon"
|
|
class="w-6 h-6 text-gray-600 group-hover:text-monaco-600 mx-auto mb-2"
|
|
/>
|
|
<span class="text-xs text-gray-700 group-hover:text-monaco-600 font-medium">
|
|
{{ action.label }}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import {
|
|
Menu, Bell, Plus, ArrowRight, Calendar, Activity, Zap,
|
|
Users, DollarSign, FileText, Mail, TrendingUp, Settings,
|
|
LayoutDashboard, UserPlus, Send, Download
|
|
} from 'lucide-vue-next'
|
|
|
|
// Import our glass components
|
|
import GlassSidebar from '~/components/GlassSidebar.vue'
|
|
import GlassStatCard from '~/components/GlassStatCard.vue'
|
|
import GlassDuesCard from '~/components/GlassDuesCard.vue'
|
|
|
|
// Page configuration
|
|
definePageMeta({
|
|
layout: 'board',
|
|
middleware: 'board-auth'
|
|
})
|
|
|
|
const router = useRouter()
|
|
|
|
// Reactive state
|
|
const sidebarOpen = ref(false)
|
|
const isMobile = ref(false)
|
|
const showQuickActions = ref(false)
|
|
const activeDuesTab = ref('overdue')
|
|
const isLoading = ref(false)
|
|
|
|
// User data
|
|
const firstName = ref('Board Member')
|
|
const userRole = ref('Administrator')
|
|
const userAvatar = ref('/default-avatar.png')
|
|
|
|
// Current date
|
|
const currentDate = computed(() => {
|
|
return new Date().toLocaleDateString('en-US', {
|
|
weekday: 'long',
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
})
|
|
})
|
|
|
|
// Statistics data with Lucide icons
|
|
const stats = ref([
|
|
{
|
|
icon: Users,
|
|
label: 'Total Members',
|
|
value: 1234,
|
|
change: '+12%',
|
|
changeType: 'increase',
|
|
color: 'monaco',
|
|
actionLabel: 'View all members'
|
|
},
|
|
{
|
|
icon: DollarSign,
|
|
label: 'Dues Collected',
|
|
value: 45678,
|
|
prefix: '$',
|
|
change: '+8%',
|
|
changeType: 'increase',
|
|
color: 'green',
|
|
actionLabel: 'View payments'
|
|
},
|
|
{
|
|
icon: Calendar,
|
|
label: 'Upcoming Events',
|
|
value: 5,
|
|
change: '2 this week',
|
|
changeType: 'neutral',
|
|
color: 'blue',
|
|
actionLabel: 'View calendar'
|
|
},
|
|
{
|
|
icon: TrendingUp,
|
|
label: 'Growth Rate',
|
|
value: 23,
|
|
suffix: '%',
|
|
change: '+3%',
|
|
changeType: 'increase',
|
|
color: 'purple',
|
|
actionLabel: 'View report'
|
|
}
|
|
])
|
|
|
|
// Dues tabs
|
|
const duesTabs = ref([
|
|
{ id: 'overdue', label: 'Overdue', count: 12 },
|
|
{ id: 'upcoming', label: 'Upcoming', count: 24 },
|
|
{ id: 'paid', label: 'Recently Paid', count: 8 }
|
|
])
|
|
|
|
// Sample dues members data - LIMITED TO 4
|
|
const duesMembers = ref([
|
|
{
|
|
id: 1,
|
|
name: 'John Smith',
|
|
avatar: '/avatar1.jpg',
|
|
countryCode: 'US',
|
|
dueAmount: 250,
|
|
dueDate: '2024-01-15'
|
|
},
|
|
{
|
|
id: 2,
|
|
name: 'Marie Dubois',
|
|
avatar: '/avatar2.jpg',
|
|
countryCode: 'MC',
|
|
dueAmount: 250,
|
|
dueDate: '2024-01-20'
|
|
},
|
|
{
|
|
id: 3,
|
|
name: 'Alessandro Rossi',
|
|
avatar: '/avatar3.jpg',
|
|
countryCode: 'IT',
|
|
dueAmount: 250,
|
|
dueDate: '2024-01-25'
|
|
},
|
|
{
|
|
id: 4,
|
|
name: 'Emma Wilson',
|
|
avatar: '/avatar4.jpg',
|
|
countryCode: 'GB',
|
|
dueAmount: 250,
|
|
dueDate: '2024-01-30'
|
|
}
|
|
])
|
|
|
|
// Computed: visible dues members (max 4)
|
|
const visibleDuesMembers = computed(() => {
|
|
return duesMembers.value.slice(0, 4)
|
|
})
|
|
|
|
const totalDuesMembers = computed(() => {
|
|
const tab = duesTabs.value.find(t => t.id === activeDuesTab.value)
|
|
return tab ? tab.count : 0
|
|
})
|
|
|
|
// Next event
|
|
const nextEvent = ref({
|
|
title: 'Annual Gala Dinner',
|
|
date: 'January 28, 2024',
|
|
attendees: 150
|
|
})
|
|
|
|
// Recent activity
|
|
const recentActivity = ref([
|
|
{ id: 1, description: 'New member John Doe joined', time: '2 hours ago' },
|
|
{ id: 2, description: 'Payment received from Jane Smith', time: '5 hours ago' },
|
|
{ id: 3, description: 'Event "Wine Tasting" created', time: '1 day ago' }
|
|
])
|
|
|
|
// Quick actions
|
|
const quickActions = [
|
|
{ icon: UserPlus, label: 'Add Member', handler: () => router.push('/board/members/new') },
|
|
{ icon: Calendar, label: 'New Event', handler: () => router.push('/board/events/new') },
|
|
{ icon: Send, label: 'Send Email', handler: () => router.push('/board/communications') },
|
|
{ icon: Download, label: 'Export Data', handler: () => generateReport() }
|
|
]
|
|
|
|
// Event handlers
|
|
const handleStatClick = (stat) => {
|
|
console.log('Stat clicked:', stat.label)
|
|
}
|
|
|
|
const handleMarkPaid = (member) => {
|
|
console.log('Mark paid:', member.name)
|
|
// Implement payment marking logic
|
|
}
|
|
|
|
const handleSendReminder = (member) => {
|
|
console.log('Send reminder:', member.name)
|
|
// Implement reminder logic
|
|
}
|
|
|
|
const handleViewDetails = (member) => {
|
|
router.push(`/board/members/${member.id}`)
|
|
}
|
|
|
|
const navigateToFullDuesList = () => {
|
|
router.push('/board/dues')
|
|
}
|
|
|
|
const handleLogout = () => {
|
|
// Implement logout logic
|
|
router.push('/logout')
|
|
}
|
|
|
|
const generateReport = () => {
|
|
console.log('Generating report...')
|
|
// Implement report generation
|
|
}
|
|
|
|
// Check if mobile
|
|
const checkMobile = () => {
|
|
isMobile.value = window.innerWidth < 1024
|
|
if (!isMobile.value) {
|
|
sidebarOpen.value = false
|
|
}
|
|
}
|
|
|
|
// Lifecycle
|
|
onMounted(() => {
|
|
checkMobile()
|
|
window.addEventListener('resize', checkMobile)
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('resize', checkMobile)
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Any additional custom styles */
|
|
</style> |