feat: Reorganize platform into member, board, and admin sections
Some checks failed
Build And Push Image / docker (push) Failing after 55s
Some checks failed
Build And Push Image / docker (push) Failing after 55s
Major platform reorganization implementing role-based portal sections: ## Infrastructure Changes - Created role-based middleware for member, board, and admin access - Updated main dashboard router to redirect based on highest privilege - Implemented access hierarchy: Admin > Board > Member ## New Layouts - Member layout: Simplified navigation for regular members - Board layout: Enhanced tools for board member management - Admin layout: Full system administration capabilities ## Member Portal (/member/*) - Dashboard: Profile overview, events, payments, activity tracking - Events: Browse, register, and manage event participation - Profile: Complete personal and professional information management - Resources: Access to documents, guides, FAQs, and quick links ## Board Portal (/board/*) - Dashboard: Statistics, dues management, board-specific tools - Members: Comprehensive member management with filtering ## Admin Portal (/admin/*) - Dashboard: System overview and administrative controls (existing) ## Design Implementation - Monaco red (#dc2626) as primary accent color - Modern card-based layouts with consistent spacing - Responsive design for all screen sizes - Glass morphism effects for enhanced visual appeal This reorganization provides clear separation of concerns based on user privileges while maintaining a cohesive user experience across all sections. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
350
pages/board/dashboard/index.vue
Normal file
350
pages/board/dashboard/index.vue
Normal file
@@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<!-- Dues Payment Banner -->
|
||||
<DuesPaymentBanner />
|
||||
|
||||
<!-- Welcome Header -->
|
||||
<v-row class="mb-6">
|
||||
<v-col>
|
||||
<h1 class="text-h3 font-weight-bold" style="color: #a31515;">
|
||||
Welcome Back, {{ firstName }}!
|
||||
</h1>
|
||||
<p class="text-h6 text-medium-emphasis">
|
||||
MonacoUSA Board Portal
|
||||
</p>
|
||||
<div class="text-center">
|
||||
<v-chip color="primary" variant="elevated" class="mt-2">
|
||||
<v-icon start>mdi-shield-account</v-icon>
|
||||
Board Member
|
||||
</v-chip>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Board Tools -->
|
||||
<v-row class="mb-6">
|
||||
<v-col cols="12" md="6">
|
||||
<v-card class="pa-4 text-center" elevation="2" hover>
|
||||
<v-icon size="48" color="primary" class="mb-2">mdi-calendar</v-icon>
|
||||
<h3 class="mb-2">Events</h3>
|
||||
<p class="text-body-2 mb-4">View and manage association events</p>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
style="border-color: #a31515; color: #a31515;"
|
||||
@click="navigateToEvents"
|
||||
>
|
||||
View Events
|
||||
</v-btn>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-card class="pa-4 text-center" elevation="2" hover>
|
||||
<v-icon size="48" color="primary" class="mb-2">mdi-account-group</v-icon>
|
||||
<h3 class="mb-2">Members</h3>
|
||||
<p class="text-body-2 mb-4">View and manage association members</p>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
style="border-color: #a31515; color: #a31515;"
|
||||
@click="navigateToMembers"
|
||||
>
|
||||
View Members
|
||||
</v-btn>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Board Statistics -->
|
||||
<v-row class="mb-6">
|
||||
<v-col cols="12" md="8">
|
||||
<v-card elevation="2">
|
||||
<v-card-title class="pa-4" style="background-color: #f5f5f5;">
|
||||
<v-icon class="mr-2" color="primary">mdi-chart-box-outline</v-icon>
|
||||
Board Overview
|
||||
</v-card-title>
|
||||
<v-card-text class="pa-4">
|
||||
<v-row>
|
||||
<v-col cols="6" md="3" class="text-center">
|
||||
<div class="text-h4 font-weight-bold" style="color: #a31515;">{{ stats.totalMembers }}</div>
|
||||
<div class="text-body-2">Total Members</div>
|
||||
</v-col>
|
||||
<v-col cols="6" md="3" class="text-center">
|
||||
<div class="text-h4 font-weight-bold" style="color: #a31515;">{{ stats.activeMembers }}</div>
|
||||
<div class="text-body-2">Active Members</div>
|
||||
</v-col>
|
||||
<v-col cols="6" md="6" class="text-center">
|
||||
<div class="text-h4 font-weight-bold" style="color: #a31515;">{{ stats.upcomingEvents }}</div>
|
||||
<div class="text-body-2">Upcoming Events</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="4">
|
||||
<v-card elevation="2">
|
||||
<v-card-title class="pa-4" style="background-color: #f5f5f5;">
|
||||
<v-icon class="mr-2" color="primary">mdi-calendar-today</v-icon>
|
||||
Next Event
|
||||
</v-card-title>
|
||||
<v-card-text class="pa-4">
|
||||
<div class="text-h6 mb-2">{{ nextEvent.title }}</div>
|
||||
<div class="text-body-2 mb-2">
|
||||
<v-icon size="small" class="mr-1">mdi-calendar</v-icon>
|
||||
{{ nextEvent.date }}
|
||||
</div>
|
||||
<div class="text-body-2 mb-4">
|
||||
<v-icon size="small" class="mr-1">mdi-clock</v-icon>
|
||||
{{ nextEvent.time }}
|
||||
</div>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
style="border-color: #a31515; color: #a31515;"
|
||||
@click="viewEventDetails"
|
||||
>
|
||||
View Details
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Dues Management Section -->
|
||||
<v-row class="mb-6">
|
||||
<v-col cols="12">
|
||||
<BoardDuesManagement
|
||||
:refresh-trigger="duesRefreshTrigger"
|
||||
@view-member="handleViewMember"
|
||||
@view-all-members="navigateToMembers"
|
||||
@member-updated="handleMemberUpdated"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
||||
<!-- View Member Dialog -->
|
||||
<ViewMemberDialog
|
||||
v-model="showViewDialog"
|
||||
:member="selectedMember"
|
||||
@edit="handleEditMember"
|
||||
/>
|
||||
|
||||
<!-- Edit Member Dialog -->
|
||||
<EditMemberDialog
|
||||
v-model="showEditDialog"
|
||||
:member="selectedMember"
|
||||
@member-updated="handleMemberUpdated"
|
||||
/>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Member } from '~/utils/types';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
middleware: 'auth'
|
||||
});
|
||||
|
||||
const { firstName, isBoard, isAdmin } = useAuth();
|
||||
|
||||
// Check board access on mount
|
||||
onMounted(() => {
|
||||
if (!isBoard.value && !isAdmin.value) {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Access denied. Board membership required.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Dues management state
|
||||
const duesRefreshTrigger = ref(0);
|
||||
|
||||
// Member dialog state
|
||||
const showViewDialog = ref(false);
|
||||
const showEditDialog = ref(false);
|
||||
const selectedMember = ref<Member | null>(null);
|
||||
|
||||
// Real data for board dashboard
|
||||
const stats = ref({
|
||||
totalMembers: 0,
|
||||
activeMembers: 0,
|
||||
upcomingEvents: 0
|
||||
});
|
||||
|
||||
const nextEvent = ref({
|
||||
id: null,
|
||||
title: 'Next Event',
|
||||
date: 'Loading...',
|
||||
time: 'Loading...',
|
||||
location: 'TBD',
|
||||
description: 'Upcoming association event'
|
||||
});
|
||||
|
||||
const isLoading = ref(true);
|
||||
|
||||
// Load real data on component mount
|
||||
onMounted(async () => {
|
||||
await loadBoardData();
|
||||
});
|
||||
|
||||
const loadBoardData = async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
// Load board statistics
|
||||
const [statsResponse, meetingResponse] = await Promise.allSettled([
|
||||
$fetch('/api/board/stats'),
|
||||
$fetch('/api/board/next-meeting')
|
||||
]);
|
||||
|
||||
// Handle stats response
|
||||
if (statsResponse.status === 'fulfilled') {
|
||||
const statsData = statsResponse.value as any;
|
||||
if (statsData?.success) {
|
||||
stats.value = {
|
||||
totalMembers: statsData.data.totalMembers || 0,
|
||||
activeMembers: statsData.data.activeMembers || 0,
|
||||
upcomingEvents: statsData.data.upcomingEvents || 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle next meeting response
|
||||
if (meetingResponse.status === 'fulfilled') {
|
||||
const meetingData = meetingResponse.value as any;
|
||||
if (meetingData?.success) {
|
||||
nextEvent.value = {
|
||||
id: meetingData.data.id,
|
||||
title: meetingData.data.title || 'Next Event',
|
||||
date: meetingData.data.date || 'TBD',
|
||||
time: meetingData.data.time || 'TBD',
|
||||
location: meetingData.data.location || 'TBD',
|
||||
description: meetingData.data.description || 'Upcoming association event'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading board data:', error);
|
||||
// Keep fallback values
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const recentActivity = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: 'Monthly Board Meeting',
|
||||
description: 'Meeting minutes approved and distributed',
|
||||
type: 'success',
|
||||
status: 'Completed'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Budget Review',
|
||||
description: 'Q4 financial report under review',
|
||||
type: 'warning',
|
||||
status: 'In Progress'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Member Application',
|
||||
description: 'New member application pending approval',
|
||||
type: 'info',
|
||||
status: 'Pending'
|
||||
}
|
||||
]);
|
||||
|
||||
// Dues management handlers
|
||||
const handleViewMember = (member: Member) => {
|
||||
// Open the view dialog instead of navigating away
|
||||
selectedMember.value = member;
|
||||
showViewDialog.value = true;
|
||||
};
|
||||
|
||||
const handleEditMember = (member: Member) => {
|
||||
// Close the view dialog and open the edit dialog
|
||||
showViewDialog.value = false;
|
||||
selectedMember.value = member;
|
||||
showEditDialog.value = true;
|
||||
};
|
||||
|
||||
const handleMemberUpdated = (member: Member) => {
|
||||
console.log('Member updated:', member.FullName || `${member.first_name} ${member.last_name}`);
|
||||
|
||||
// Close edit dialog
|
||||
showEditDialog.value = false;
|
||||
|
||||
// Trigger dues refresh to update the lists
|
||||
duesRefreshTrigger.value += 1;
|
||||
|
||||
// You could also update stats here if needed
|
||||
// stats.value = await fetchUpdatedStats();
|
||||
};
|
||||
|
||||
// Navigation methods
|
||||
const navigateToEvents = () => {
|
||||
// Navigate to events page
|
||||
navigateTo('/dashboard/events');
|
||||
};
|
||||
|
||||
const navigateToMembers = () => {
|
||||
// Navigate to member list page
|
||||
navigateTo('/dashboard/member-list');
|
||||
};
|
||||
|
||||
const viewEventDetails = () => {
|
||||
console.log('View event details');
|
||||
};
|
||||
|
||||
const scheduleNewMeeting = () => {
|
||||
console.log('Schedule new meeting');
|
||||
};
|
||||
|
||||
const createAnnouncement = () => {
|
||||
console.log('Create announcement');
|
||||
};
|
||||
|
||||
const generateReport = () => {
|
||||
console.log('Generate report');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card {
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
.v-card:hover {
|
||||
transform: translateY(-2px);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.v-btn {
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.v-icon {
|
||||
color: #a31515 !important;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text-body-2 {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.v-chip {
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user