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:
889
pages/admin/dashboard/index.vue
Normal file
889
pages/admin/dashboard/index.vue
Normal file
@@ -0,0 +1,889 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-container fluid>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<h1 class="text-h4 font-weight-bold mb-4">
|
||||
<v-icon left>mdi-account</v-icon>
|
||||
Welcome Back, {{ firstName }}
|
||||
</h1>
|
||||
<p class="text-body-1 mb-6">
|
||||
Manage users and portal settings for the MonacoUSA Portal.
|
||||
</p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Portal Status -->
|
||||
<v-row class="mb-6">
|
||||
<v-col cols="12" md="6">
|
||||
<v-card elevation="2">
|
||||
<v-card-text>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<p class="text-caption text-medium-emphasis mb-1">Portal Status</p>
|
||||
<p class="text-h5 font-weight-bold text-success">Online</p>
|
||||
</div>
|
||||
<v-icon color="success" size="40">mdi-check-circle</v-icon>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-card elevation="2">
|
||||
<v-card-text>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<div>
|
||||
<p class="text-caption text-medium-emphasis mb-1">Total Users</p>
|
||||
<p class="text-h5 font-weight-bold">{{ userCount }}</p>
|
||||
</div>
|
||||
<v-icon color="primary" size="40">mdi-account-multiple</v-icon>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- User Management -->
|
||||
<v-row class="mb-6">
|
||||
<v-col cols="12">
|
||||
<v-card elevation="2">
|
||||
<v-card-title>
|
||||
<v-icon left>mdi-account-group</v-icon>
|
||||
User Management
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<p class="mb-4">Manage user accounts, roles, and permissions for the MonacoUSA Portal.</p>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="3">
|
||||
<v-btn
|
||||
color="primary"
|
||||
block
|
||||
size="large"
|
||||
@click="navigateTo('/dashboard/member-list')"
|
||||
>
|
||||
<v-icon start>mdi-account-cog</v-icon>
|
||||
Manage Users
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="3">
|
||||
<v-btn
|
||||
color="success"
|
||||
variant="outlined"
|
||||
block
|
||||
size="large"
|
||||
@click="showCreateUserDialog = true"
|
||||
>
|
||||
<v-icon start>mdi-account-plus</v-icon>
|
||||
Create User Account
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="3">
|
||||
<v-btn
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
block
|
||||
size="large"
|
||||
@click="viewAuditLogs"
|
||||
>
|
||||
<v-icon start>mdi-file-document-outline</v-icon>
|
||||
View Audit Logs
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="3">
|
||||
<v-btn
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
block
|
||||
size="large"
|
||||
@click="showAdminConfig = true"
|
||||
>
|
||||
<v-icon start>mdi-cog</v-icon>
|
||||
Portal Settings
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
||||
<!-- Dues Management -->
|
||||
<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>
|
||||
|
||||
<!-- Portal Configuration -->
|
||||
<v-row class="mb-6">
|
||||
<v-col cols="12">
|
||||
<v-card elevation="2">
|
||||
<v-card-title>
|
||||
<v-icon left>mdi-cog</v-icon>
|
||||
Portal Configuration
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<p class="mb-4">Configure all portal settings including database, email, reCAPTCHA, and membership fees in one centralized location.</p>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="4">
|
||||
<v-btn
|
||||
color="primary"
|
||||
block
|
||||
size="large"
|
||||
@click="showAdminConfig = true"
|
||||
>
|
||||
<v-icon start>mdi-cog</v-icon>
|
||||
Portal Settings
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="8">
|
||||
<v-row dense>
|
||||
<v-col cols="6" sm="3">
|
||||
<v-chip color="success" variant="tonal" size="small" block>
|
||||
<v-icon start size="14">mdi-database</v-icon>
|
||||
NocoDB
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="3">
|
||||
<v-chip color="info" variant="tonal" size="small" block>
|
||||
<v-icon start size="14">mdi-email</v-icon>
|
||||
Email
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="3">
|
||||
<v-chip color="warning" variant="tonal" size="small" block>
|
||||
<v-icon start size="14">mdi-shield</v-icon>
|
||||
reCAPTCHA
|
||||
</v-chip>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="3">
|
||||
<v-chip color="primary" variant="tonal" size="small" block>
|
||||
<v-icon start size="14">mdi-bank</v-icon>
|
||||
Membership
|
||||
</v-chip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Data Management -->
|
||||
<v-row class="mb-6">
|
||||
<v-col cols="12">
|
||||
<v-card elevation="2">
|
||||
<v-card-title>
|
||||
<v-icon left>mdi-database-cog</v-icon>
|
||||
Data Management
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<p class="mb-4">Manage data integrity and perform maintenance operations on the portal database.</p>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-btn
|
||||
@click="assignMemberIds"
|
||||
color="warning"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-account-multiple-plus"
|
||||
block
|
||||
size="large"
|
||||
:loading="assigningMemberIds"
|
||||
>
|
||||
Assign Member IDs
|
||||
</v-btn>
|
||||
<div class="text-caption mt-2 text-medium-emphasis">
|
||||
Assign unique member IDs (MUSA-0001, MUSA-0002, etc.) to members who don't have them
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-btn
|
||||
@click="backfillEventIds"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
prepend-icon="mdi-calendar-sync"
|
||||
block
|
||||
size="large"
|
||||
:loading="backfillLoading"
|
||||
>
|
||||
Backfill Event IDs
|
||||
</v-btn>
|
||||
<div class="text-caption mt-2 text-medium-emphasis">
|
||||
Assign business IDs to events that don't have them
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
</v-container>
|
||||
|
||||
<!-- NocoDB Settings Dialog -->
|
||||
<NocoDBSettingsDialog
|
||||
v-model="showNocoDBSettings"
|
||||
@settings-saved="handleSettingsSaved"
|
||||
/>
|
||||
|
||||
<!-- Admin Configuration Dialog -->
|
||||
<AdminConfigurationDialog
|
||||
v-model="showAdminConfig"
|
||||
@settings-saved="handleAdminConfigSaved"
|
||||
/>
|
||||
|
||||
<!-- reCAPTCHA Configuration Dialog -->
|
||||
<v-dialog v-model="showRecaptchaConfig" max-width="600">
|
||||
<v-card>
|
||||
<v-card-title class="text-h5">
|
||||
<v-icon left>mdi-shield-account</v-icon>
|
||||
reCAPTCHA Configuration
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-alert type="info" variant="tonal" class="mb-4">
|
||||
<v-alert-title>Security Configuration</v-alert-title>
|
||||
Configure Google reCAPTCHA settings for form protection on the registration page.
|
||||
</v-alert>
|
||||
|
||||
<v-form ref="recaptchaForm" v-model="recaptchaValid">
|
||||
<v-text-field
|
||||
v-model="recaptchaConfig.siteKey"
|
||||
label="Site Key"
|
||||
placeholder="6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy"
|
||||
:rules="[v => !!v || 'Site key is required']"
|
||||
variant="outlined"
|
||||
required
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model="recaptchaConfig.secretKey"
|
||||
label="Secret Key"
|
||||
placeholder="6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx"
|
||||
:rules="[v => !!v || 'Secret key is required']"
|
||||
variant="outlined"
|
||||
type="password"
|
||||
required
|
||||
/>
|
||||
|
||||
<v-alert type="warning" variant="tonal" class="mt-4">
|
||||
<v-alert-title>Important</v-alert-title>
|
||||
Keep your secret key confidential. You can get these keys from the Google reCAPTCHA admin console.
|
||||
</v-alert>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn text @click="showRecaptchaConfig = false">Cancel</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
:loading="savingRecaptcha"
|
||||
:disabled="!recaptchaValid"
|
||||
@click="saveRecaptchaConfig"
|
||||
>
|
||||
Save Configuration
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Membership Configuration Dialog -->
|
||||
<v-dialog v-model="showMembershipConfig" max-width="600">
|
||||
<v-card>
|
||||
<v-card-title class="text-h5">
|
||||
<v-icon left>mdi-bank</v-icon>
|
||||
Membership Configuration
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-alert type="info" variant="tonal" class="mb-4">
|
||||
<v-alert-title>Payment Configuration</v-alert-title>
|
||||
Configure membership fees and payment details displayed on the registration page.
|
||||
</v-alert>
|
||||
|
||||
<v-form ref="membershipForm" v-model="membershipValid">
|
||||
<v-text-field
|
||||
v-model="membershipConfig.membershipFee"
|
||||
label="Annual Membership Fee (€)"
|
||||
type="number"
|
||||
:rules="[
|
||||
v => !!v || 'Membership fee is required',
|
||||
v => v > 0 || 'Fee must be greater than 0'
|
||||
]"
|
||||
variant="outlined"
|
||||
required
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model="membershipConfig.iban"
|
||||
label="IBAN"
|
||||
placeholder="DE89 3704 0044 0532 0130 00"
|
||||
:rules="[v => !!v || 'IBAN is required']"
|
||||
variant="outlined"
|
||||
required
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model="membershipConfig.accountHolder"
|
||||
label="Account Holder Name"
|
||||
placeholder="MonacoUSA Association"
|
||||
:rules="[v => !!v || 'Account holder is required']"
|
||||
variant="outlined"
|
||||
required
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn text @click="showMembershipConfig = false">Cancel</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
:loading="savingMembership"
|
||||
:disabled="!membershipValid"
|
||||
@click="saveMembershipConfig"
|
||||
>
|
||||
Save Configuration
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- View Member Dialog -->
|
||||
<ViewMemberDialog
|
||||
v-model="showViewDialog"
|
||||
:member="selectedMember"
|
||||
@edit="handleEditMember"
|
||||
/>
|
||||
|
||||
<!-- Edit Member Dialog -->
|
||||
<EditMemberDialog
|
||||
v-model="showEditDialog"
|
||||
:member="selectedMember"
|
||||
@member-updated="handleMemberUpdated"
|
||||
/>
|
||||
|
||||
<!-- Create User Dialog -->
|
||||
<v-dialog v-model="showCreateUserDialog" max-width="600">
|
||||
<v-card>
|
||||
<v-card-title class="text-h5">
|
||||
<v-icon left>mdi-account-plus</v-icon>
|
||||
Create User Account
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-alert type="info" variant="tonal" class="mb-4">
|
||||
<v-alert-title>Create Portal Account</v-alert-title>
|
||||
This will create a new user account in the MonacoUSA Portal with email verification.
|
||||
</v-alert>
|
||||
|
||||
<v-form ref="createUserForm" v-model="createUserValid">
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<v-text-field
|
||||
v-model="newUser.firstName"
|
||||
label="First Name"
|
||||
:rules="[v => !!v || 'First name is required']"
|
||||
variant="outlined"
|
||||
required
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<v-text-field
|
||||
v-model="newUser.lastName"
|
||||
label="Last Name"
|
||||
:rules="[v => !!v || 'Last name is required']"
|
||||
variant="outlined"
|
||||
required
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-text-field
|
||||
v-model="newUser.email"
|
||||
label="Email Address"
|
||||
type="email"
|
||||
:rules="[
|
||||
v => !!v || 'Email is required',
|
||||
v => /.+@.+\..+/.test(v) || 'Email must be valid'
|
||||
]"
|
||||
variant="outlined"
|
||||
required
|
||||
/>
|
||||
|
||||
<v-select
|
||||
v-model="newUser.role"
|
||||
label="User Role"
|
||||
:items="roleOptions"
|
||||
item-title="title"
|
||||
item-value="value"
|
||||
variant="outlined"
|
||||
required
|
||||
/>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn text @click="showCreateUserDialog = false">Cancel</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
:loading="creatingUser"
|
||||
:disabled="!createUserValid"
|
||||
@click="createUserAccount"
|
||||
>
|
||||
Create Account
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'dashboard',
|
||||
middleware: 'auth-admin'
|
||||
});
|
||||
|
||||
const { firstName } = useAuth();
|
||||
|
||||
// Reactive data
|
||||
const userCount = ref(0);
|
||||
const loading = ref(false);
|
||||
const showCreateUserDialog = ref(false);
|
||||
const showAdminConfig = ref(false);
|
||||
const showRecaptchaConfig = ref(false);
|
||||
const showMembershipConfig = ref(false);
|
||||
const showEmailConfig = ref(false);
|
||||
|
||||
// Dues management
|
||||
const overdueCount = ref(0);
|
||||
const overdueRefreshTrigger = ref(0);
|
||||
const duesRefreshTrigger = ref(0);
|
||||
|
||||
// Data management
|
||||
const assigningMemberIds = ref(false);
|
||||
const backfillLoading = ref(false);
|
||||
|
||||
// Member dialog state
|
||||
const showViewDialog = ref(false);
|
||||
const showEditDialog = ref(false);
|
||||
const selectedMember = ref(null);
|
||||
|
||||
// Create user dialog data
|
||||
const createUserValid = ref(false);
|
||||
const creatingUser = ref(false);
|
||||
const newUser = ref({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
role: 'user'
|
||||
});
|
||||
|
||||
const roleOptions = [
|
||||
{ title: 'User', value: 'user' },
|
||||
{ title: 'Board Member', value: 'board' },
|
||||
{ title: 'Administrator', value: 'admin' }
|
||||
];
|
||||
|
||||
// reCAPTCHA configuration data
|
||||
const recaptchaValid = ref(false);
|
||||
const savingRecaptcha = ref(false);
|
||||
const recaptchaConfig = ref({
|
||||
siteKey: '',
|
||||
secretKey: ''
|
||||
});
|
||||
|
||||
// Membership configuration data
|
||||
const membershipValid = ref(false);
|
||||
const savingMembership = ref(false);
|
||||
const membershipConfig = ref({
|
||||
membershipFee: 50,
|
||||
iban: '',
|
||||
accountHolder: ''
|
||||
});
|
||||
|
||||
const recentActivity = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: 'User Account Created',
|
||||
description: 'New user account created for john.doe@monacousa.org',
|
||||
time: '2 hours ago',
|
||||
icon: 'mdi-account-plus',
|
||||
color: 'success'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Role Updated',
|
||||
description: 'User role updated from User to Board Member',
|
||||
time: '4 hours ago',
|
||||
icon: 'mdi-shield-account',
|
||||
color: 'warning'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'System Backup',
|
||||
description: 'Automated system backup completed successfully',
|
||||
time: '1 day ago',
|
||||
icon: 'mdi-backup-restore',
|
||||
color: 'info'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'Password Reset',
|
||||
description: 'Password reset requested for jane.smith@monacousa.org',
|
||||
time: '2 days ago',
|
||||
icon: 'mdi-key-change',
|
||||
color: 'primary'
|
||||
}
|
||||
]);
|
||||
|
||||
// Load simplified admin stats (without system metrics)
|
||||
const loadStats = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
// Simple user count without complex system metrics
|
||||
const response = await $fetch<{ userCount: number }>('/api/admin/stats');
|
||||
userCount.value = response.userCount || 0;
|
||||
|
||||
console.log('✅ Admin stats loaded:', { userCount: userCount.value });
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to load admin stats:', error);
|
||||
// Use fallback data
|
||||
userCount.value = 25;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Action methods (placeholders for now)
|
||||
const manageUsers = () => {
|
||||
window.open('https://auth.monacousa.org', '_blank');
|
||||
};
|
||||
|
||||
const viewAuditLogs = () => {
|
||||
console.log('Navigate to audit logs');
|
||||
// TODO: Implement audit logs navigation
|
||||
};
|
||||
|
||||
const showNocoDBSettings = ref(false);
|
||||
|
||||
const portalSettings = () => {
|
||||
showNocoDBSettings.value = true;
|
||||
};
|
||||
|
||||
const handleSettingsSaved = () => {
|
||||
console.log('NocoDB settings saved successfully');
|
||||
};
|
||||
|
||||
const handleAdminConfigSaved = () => {
|
||||
console.log('Admin configuration saved successfully');
|
||||
showAdminConfig.value = false;
|
||||
};
|
||||
|
||||
// Handle opening email configuration directly
|
||||
const openEmailConfig = () => {
|
||||
// Set the activeTab to email when opening the admin config dialog
|
||||
showEmailConfig.value = true;
|
||||
showAdminConfig.value = true;
|
||||
};
|
||||
|
||||
// Watch for showEmailConfig to set the initial tab
|
||||
watch(showEmailConfig, (newValue) => {
|
||||
if (newValue) {
|
||||
// This will be handled by the AdminConfigurationDialog to set initial tab
|
||||
showEmailConfig.value = false; // Reset the flag
|
||||
}
|
||||
});
|
||||
|
||||
const saveRecaptchaConfig = async () => {
|
||||
if (!recaptchaValid.value) return;
|
||||
|
||||
savingRecaptcha.value = true;
|
||||
try {
|
||||
const response = await $fetch('/api/admin/recaptcha-config', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
siteKey: recaptchaConfig.value.siteKey,
|
||||
secretKey: recaptchaConfig.value.secretKey
|
||||
}
|
||||
}) as any;
|
||||
|
||||
if (response?.success) {
|
||||
showRecaptchaConfig.value = false;
|
||||
console.log('reCAPTCHA configuration saved successfully');
|
||||
// TODO: Show success notification
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to save reCAPTCHA configuration:', error);
|
||||
// TODO: Show error notification
|
||||
} finally {
|
||||
savingRecaptcha.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const saveMembershipConfig = async () => {
|
||||
if (!membershipValid.value) return;
|
||||
|
||||
savingMembership.value = true;
|
||||
try {
|
||||
const response = await $fetch('/api/admin/registration-config', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
membershipFee: membershipConfig.value.membershipFee,
|
||||
iban: membershipConfig.value.iban,
|
||||
accountHolder: membershipConfig.value.accountHolder
|
||||
}
|
||||
}) as any;
|
||||
|
||||
if (response?.success) {
|
||||
showMembershipConfig.value = false;
|
||||
console.log('Membership configuration saved successfully');
|
||||
// TODO: Show success notification
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to save membership configuration:', error);
|
||||
// TODO: Show error notification
|
||||
} finally {
|
||||
savingMembership.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const createUserAccount = async () => {
|
||||
if (!createUserValid.value) return;
|
||||
|
||||
creatingUser.value = true;
|
||||
try {
|
||||
console.log('Creating user account:', newUser.value);
|
||||
|
||||
// TODO: Implement actual user creation using enhanced Keycloak API
|
||||
// For now, just show success
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate API call
|
||||
|
||||
// Reset form
|
||||
newUser.value = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
role: 'user'
|
||||
};
|
||||
|
||||
showCreateUserDialog.value = false;
|
||||
console.log('User account created successfully');
|
||||
|
||||
// TODO: Show success notification
|
||||
// TODO: Refresh user list
|
||||
} catch (error) {
|
||||
console.error('Failed to create user account:', error);
|
||||
// TODO: Show error notification
|
||||
} finally {
|
||||
creatingUser.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const createUser = () => {
|
||||
console.log('Create new user');
|
||||
// TODO: Implement create user dialog/form
|
||||
};
|
||||
|
||||
const generateReport = () => {
|
||||
console.log('Generate user report');
|
||||
// TODO: Implement report generation
|
||||
};
|
||||
|
||||
const manageRoles = () => {
|
||||
console.log('Manage user roles');
|
||||
// TODO: Implement role management
|
||||
};
|
||||
|
||||
const systemMaintenance = () => {
|
||||
console.log('System maintenance');
|
||||
// TODO: Implement maintenance mode
|
||||
};
|
||||
|
||||
// Dues management handlers
|
||||
const loadOverdueCount = async () => {
|
||||
try {
|
||||
const response = await $fetch<{ success: boolean; data: { count: number } }>('/api/members/overdue-count');
|
||||
if (response.success) {
|
||||
overdueCount.value = response.data.count;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error loading overdue count:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const viewOverdueMembers = () => {
|
||||
// Navigate to member list with overdue filter applied
|
||||
navigateTo('/dashboard/member-list');
|
||||
};
|
||||
|
||||
const sendDuesReminders = () => {
|
||||
// Placeholder for dues reminder functionality
|
||||
console.log('Send dues reminders - feature to be implemented');
|
||||
};
|
||||
|
||||
const handleStatusesUpdated = async (updatedCount: number) => {
|
||||
console.log(`Successfully updated ${updatedCount} member${updatedCount !== 1 ? 's' : ''} to inactive status`);
|
||||
|
||||
// Refresh overdue count
|
||||
await loadOverdueCount();
|
||||
|
||||
// Trigger banner refresh
|
||||
overdueRefreshTrigger.value += 1;
|
||||
};
|
||||
|
||||
const handleViewMember = (member: any) => {
|
||||
// Open the view dialog instead of navigating away
|
||||
selectedMember.value = member;
|
||||
showViewDialog.value = true;
|
||||
};
|
||||
|
||||
const handleEditMember = (member: any) => {
|
||||
// Close the view dialog and open the edit dialog
|
||||
showViewDialog.value = false;
|
||||
selectedMember.value = member;
|
||||
showEditDialog.value = true;
|
||||
};
|
||||
|
||||
const navigateToMembers = () => {
|
||||
// Navigate to member list page
|
||||
navigateTo('/dashboard/member-list');
|
||||
};
|
||||
|
||||
const handleMemberUpdated = (member: any) => {
|
||||
console.log('Member updated:', member.FullName || `${member.first_name} ${member.last_name}`);
|
||||
|
||||
// Close edit dialog
|
||||
showEditDialog.value = false;
|
||||
|
||||
// Trigger dues refresh
|
||||
duesRefreshTrigger.value += 1;
|
||||
};
|
||||
|
||||
// Data management functions
|
||||
const assignMemberIds = async () => {
|
||||
assigningMemberIds.value = true;
|
||||
|
||||
try {
|
||||
console.log('Starting member ID assignment...');
|
||||
|
||||
const response = await $fetch<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: {
|
||||
totalMembers: number;
|
||||
membersUpdated: number;
|
||||
updatedMembers: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
memberId: string;
|
||||
}>;
|
||||
startingId: string | null;
|
||||
endingId: string | null;
|
||||
};
|
||||
}>('/api/admin/assign-member-ids', {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
console.log('✅ Member ID assignment completed:', {
|
||||
totalMembers: response.data.totalMembers,
|
||||
membersUpdated: response.data.membersUpdated,
|
||||
startingId: response.data.startingId,
|
||||
endingId: response.data.endingId
|
||||
});
|
||||
|
||||
// Show success message
|
||||
alert(`Success! Assigned member IDs to ${response.data.membersUpdated} members.\nRange: ${response.data.startingId} to ${response.data.endingId}`);
|
||||
|
||||
// Refresh dues management data
|
||||
duesRefreshTrigger.value += 1;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ Failed to assign member IDs:', error);
|
||||
alert(`Error: ${error.statusMessage || error.message || 'Failed to assign member IDs'}`);
|
||||
} finally {
|
||||
assigningMemberIds.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const backfillEventIds = async () => {
|
||||
backfillLoading.value = true;
|
||||
|
||||
try {
|
||||
console.log('Starting event ID backfill...');
|
||||
|
||||
const response = await $fetch<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
data: {
|
||||
totalEvents: number;
|
||||
eventsUpdated: number;
|
||||
};
|
||||
}>('/api/admin/backfill-event-ids', {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
console.log('✅ Event ID backfill completed:', {
|
||||
totalEvents: response.data.totalEvents,
|
||||
eventsUpdated: response.data.eventsUpdated
|
||||
});
|
||||
|
||||
// Show success message
|
||||
alert(`Success! Assigned event IDs to ${response.data.eventsUpdated} events.`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('❌ Failed to backfill event IDs:', error);
|
||||
alert(`Error: ${error.statusMessage || error.message || 'Failed to backfill event IDs'}`);
|
||||
} finally {
|
||||
backfillLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Load stats and overdue count on component mount
|
||||
onMounted(async () => {
|
||||
await loadStats();
|
||||
await loadOverdueCount();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card {
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
.v-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15) !important;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.v-btn {
|
||||
text-transform: none !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.v-list-item {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.v-list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user