monacousa-portal/pages/board/dashboard/index-glass.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>