2025-08-07 12:28:41 +02:00
|
|
|
<template>
|
2025-08-07 16:20:05 +02:00
|
|
|
<div>
|
|
|
|
|
<v-container fluid>
|
|
|
|
|
<v-row>
|
|
|
|
|
<v-col cols="12">
|
|
|
|
|
<h1 class="text-h4 font-weight-bold mb-4">
|
2025-08-07 19:20:29 +02:00
|
|
|
<v-icon left>mdi-account</v-icon>
|
|
|
|
|
Welcome Back, {{ firstName }}
|
2025-08-07 16:20:05 +02:00
|
|
|
</h1>
|
|
|
|
|
<p class="text-body-1 mb-6">
|
|
|
|
|
Manage users and portal settings for the MonacoUSA Portal.
|
|
|
|
|
</p>
|
|
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
2025-08-07 12:28:41 +02:00
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
<!-- 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>
|
2025-08-07 12:28:41 +02:00
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
<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>
|
2025-08-07 12:28:41 +02:00
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
<!-- 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>
|
2025-08-08 19:55:32 +02:00
|
|
|
<v-col cols="12" md="3">
|
2025-08-07 16:20:05 +02:00
|
|
|
<v-btn
|
|
|
|
|
color="primary"
|
|
|
|
|
block
|
|
|
|
|
size="large"
|
2025-08-08 19:55:32 +02:00
|
|
|
@click="navigateTo('/dashboard/member-list')"
|
2025-08-07 16:20:05 +02:00
|
|
|
>
|
|
|
|
|
<v-icon start>mdi-account-cog</v-icon>
|
|
|
|
|
Manage Users
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-col>
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
<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">
|
2025-08-07 16:20:05 +02:00
|
|
|
<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>
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
<v-col cols="12" md="3">
|
2025-08-07 16:20:05 +02:00
|
|
|
<v-btn
|
|
|
|
|
color="secondary"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
block
|
|
|
|
|
size="large"
|
2025-08-08 19:55:32 +02:00
|
|
|
@click="showAdminConfig = true"
|
2025-08-07 16:20:05 +02:00
|
|
|
>
|
|
|
|
|
<v-icon start>mdi-cog</v-icon>
|
|
|
|
|
Portal Settings
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
|
|
|
|
</v-card-text>
|
|
|
|
|
</v-card>
|
|
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
2025-08-07 12:28:41 +02:00
|
|
|
|
2025-08-10 23:29:48 +02:00
|
|
|
|
|
|
|
|
<!-- 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>
|
|
|
|
|
|
2025-08-09 16:05:00 +02:00
|
|
|
<!-- Portal Configuration -->
|
2025-08-07 19:57:03 +02:00
|
|
|
<v-row class="mb-6">
|
|
|
|
|
<v-col cols="12">
|
|
|
|
|
<v-card elevation="2">
|
|
|
|
|
<v-card-title>
|
2025-08-09 16:05:00 +02:00
|
|
|
<v-icon left>mdi-cog</v-icon>
|
|
|
|
|
Portal Configuration
|
2025-08-07 19:57:03 +02:00
|
|
|
</v-card-title>
|
|
|
|
|
<v-card-text>
|
2025-08-09 16:05:00 +02:00
|
|
|
<p class="mb-4">Configure all portal settings including database, email, reCAPTCHA, and membership fees in one centralized location.</p>
|
2025-08-07 19:57:03 +02:00
|
|
|
|
|
|
|
|
<v-row>
|
2025-08-09 16:05:00 +02:00
|
|
|
<v-col cols="12" md="4">
|
2025-08-07 19:57:03 +02:00
|
|
|
<v-btn
|
2025-08-09 16:05:00 +02:00
|
|
|
color="primary"
|
2025-08-07 19:57:03 +02:00
|
|
|
block
|
|
|
|
|
size="large"
|
2025-08-09 16:05:00 +02:00
|
|
|
@click="showAdminConfig = true"
|
2025-08-07 19:57:03 +02:00
|
|
|
>
|
2025-08-09 16:05:00 +02:00
|
|
|
<v-icon start>mdi-cog</v-icon>
|
|
|
|
|
Portal Settings
|
2025-08-07 19:57:03 +02:00
|
|
|
</v-btn>
|
|
|
|
|
</v-col>
|
|
|
|
|
|
2025-08-09 16:05:00 +02:00
|
|
|
<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>
|
2025-08-07 19:57:03 +02:00
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
|
|
|
|
</v-card-text>
|
|
|
|
|
</v-card>
|
|
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
2025-08-07 12:28:41 +02:00
|
|
|
|
2025-08-13 23:03:49 +02:00
|
|
|
<!-- 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>
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
</v-container>
|
2025-08-07 19:46:27 +02:00
|
|
|
|
|
|
|
|
<!-- NocoDB Settings Dialog -->
|
|
|
|
|
<NocoDBSettingsDialog
|
|
|
|
|
v-model="showNocoDBSettings"
|
|
|
|
|
@settings-saved="handleSettingsSaved"
|
|
|
|
|
/>
|
2025-08-08 19:55:32 +02:00
|
|
|
|
|
|
|
|
<!-- Admin Configuration Dialog -->
|
|
|
|
|
<AdminConfigurationDialog
|
|
|
|
|
v-model="showAdminConfig"
|
|
|
|
|
@settings-saved="handleAdminConfigSaved"
|
|
|
|
|
/>
|
|
|
|
|
|
2025-08-08 20:07:47 +02:00
|
|
|
<!-- 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>
|
|
|
|
|
|
2025-08-12 04:25:35 +02:00
|
|
|
<!-- View Member Dialog -->
|
|
|
|
|
<ViewMemberDialog
|
|
|
|
|
v-model="showViewDialog"
|
|
|
|
|
:member="selectedMember"
|
|
|
|
|
@edit="handleEditMember"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<!-- Edit Member Dialog -->
|
|
|
|
|
<EditMemberDialog
|
|
|
|
|
v-model="showEditDialog"
|
|
|
|
|
:member="selectedMember"
|
|
|
|
|
@member-updated="handleMemberUpdated"
|
|
|
|
|
/>
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
<!-- 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>
|
2025-08-07 16:20:05 +02:00
|
|
|
</div>
|
2025-08-07 12:28:41 +02:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
definePageMeta({
|
|
|
|
|
layout: 'dashboard',
|
2025-08-07 16:20:05 +02:00
|
|
|
middleware: 'auth-admin'
|
2025-08-07 12:28:41 +02:00
|
|
|
});
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
const { firstName } = useAuth();
|
2025-08-07 12:28:41 +02:00
|
|
|
|
|
|
|
|
// Reactive data
|
2025-08-07 16:20:05 +02:00
|
|
|
const userCount = ref(0);
|
|
|
|
|
const loading = ref(false);
|
2025-08-08 19:55:32 +02:00
|
|
|
const showCreateUserDialog = ref(false);
|
|
|
|
|
const showAdminConfig = ref(false);
|
2025-08-08 20:07:47 +02:00
|
|
|
const showRecaptchaConfig = ref(false);
|
|
|
|
|
const showMembershipConfig = ref(false);
|
2025-08-08 23:47:21 +02:00
|
|
|
const showEmailConfig = ref(false);
|
2025-08-08 19:55:32 +02:00
|
|
|
|
2025-08-10 23:29:48 +02:00
|
|
|
// Dues management
|
|
|
|
|
const overdueCount = ref(0);
|
|
|
|
|
const overdueRefreshTrigger = ref(0);
|
|
|
|
|
const duesRefreshTrigger = ref(0);
|
|
|
|
|
|
2025-08-13 23:03:49 +02:00
|
|
|
// Data management
|
|
|
|
|
const assigningMemberIds = ref(false);
|
|
|
|
|
const backfillLoading = ref(false);
|
|
|
|
|
|
2025-08-12 04:25:35 +02:00
|
|
|
// Member dialog state
|
|
|
|
|
const showViewDialog = ref(false);
|
|
|
|
|
const showEditDialog = ref(false);
|
|
|
|
|
const selectedMember = ref(null);
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
// 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' }
|
|
|
|
|
];
|
2025-08-07 12:28:41 +02:00
|
|
|
|
2025-08-08 20:07:47 +02:00
|
|
|
// 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: ''
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-07 12:28:41 +02:00
|
|
|
const recentActivity = ref([
|
|
|
|
|
{
|
|
|
|
|
id: 1,
|
2025-08-07 16:20:05 +02:00
|
|
|
title: 'User Account Created',
|
|
|
|
|
description: 'New user account created for john.doe@monacousa.org',
|
|
|
|
|
time: '2 hours ago',
|
|
|
|
|
icon: 'mdi-account-plus',
|
|
|
|
|
color: 'success'
|
2025-08-07 12:28:41 +02:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 2,
|
2025-08-07 16:20:05 +02:00
|
|
|
title: 'Role Updated',
|
|
|
|
|
description: 'User role updated from User to Board Member',
|
|
|
|
|
time: '4 hours ago',
|
|
|
|
|
icon: 'mdi-shield-account',
|
|
|
|
|
color: 'warning'
|
2025-08-07 12:28:41 +02:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 3,
|
2025-08-07 16:20:05 +02:00
|
|
|
title: 'System Backup',
|
|
|
|
|
description: 'Automated system backup completed successfully',
|
|
|
|
|
time: '1 day ago',
|
|
|
|
|
icon: 'mdi-backup-restore',
|
|
|
|
|
color: 'info'
|
2025-08-07 12:28:41 +02:00
|
|
|
},
|
|
|
|
|
{
|
2025-08-07 16:20:05 +02:00
|
|
|
id: 4,
|
|
|
|
|
title: 'Password Reset',
|
|
|
|
|
description: 'Password reset requested for jane.smith@monacousa.org',
|
|
|
|
|
time: '2 days ago',
|
|
|
|
|
icon: 'mdi-key-change',
|
|
|
|
|
color: 'primary'
|
2025-08-07 12:28:41 +02:00
|
|
|
}
|
|
|
|
|
]);
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
// Load simplified admin stats (without system metrics)
|
|
|
|
|
const loadStats = async () => {
|
2025-08-07 12:28:41 +02:00
|
|
|
try {
|
2025-08-07 16:20:05 +02:00
|
|
|
loading.value = true;
|
2025-08-07 15:51:16 +02:00
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
// Simple user count without complex system metrics
|
|
|
|
|
const response = await $fetch<{ userCount: number }>('/api/admin/stats');
|
|
|
|
|
userCount.value = response.userCount || 0;
|
2025-08-07 15:51:16 +02:00
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
console.log('✅ Admin stats loaded:', { userCount: userCount.value });
|
2025-08-07 12:28:41 +02:00
|
|
|
} catch (error) {
|
2025-08-07 16:20:05 +02:00
|
|
|
console.error('❌ Failed to load admin stats:', error);
|
|
|
|
|
// Use fallback data
|
|
|
|
|
userCount.value = 25;
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false;
|
2025-08-07 12:28:41 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
// Action methods (placeholders for now)
|
|
|
|
|
const manageUsers = () => {
|
2025-08-07 19:20:29 +02:00
|
|
|
window.open('https://auth.monacousa.org', '_blank');
|
2025-08-07 12:28:41 +02:00
|
|
|
};
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
const viewAuditLogs = () => {
|
2025-08-07 12:28:41 +02:00
|
|
|
console.log('Navigate to audit logs');
|
2025-08-07 16:20:05 +02:00
|
|
|
// TODO: Implement audit logs navigation
|
2025-08-07 12:28:41 +02:00
|
|
|
};
|
|
|
|
|
|
2025-08-07 19:46:27 +02:00
|
|
|
const showNocoDBSettings = ref(false);
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
const portalSettings = () => {
|
2025-08-07 19:46:27 +02:00
|
|
|
showNocoDBSettings.value = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleSettingsSaved = () => {
|
|
|
|
|
console.log('NocoDB settings saved successfully');
|
2025-08-07 12:28:41 +02:00
|
|
|
};
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
const handleAdminConfigSaved = () => {
|
|
|
|
|
console.log('Admin configuration saved successfully');
|
|
|
|
|
showAdminConfig.value = false;
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-08 23:47:21 +02:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-08 20:07:47 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-08 19:55:32 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
const createUser = () => {
|
2025-08-07 12:28:41 +02:00
|
|
|
console.log('Create new user');
|
2025-08-07 16:20:05 +02:00
|
|
|
// TODO: Implement create user dialog/form
|
2025-08-07 12:28:41 +02:00
|
|
|
};
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
const generateReport = () => {
|
|
|
|
|
console.log('Generate user report');
|
|
|
|
|
// TODO: Implement report generation
|
2025-08-07 12:28:41 +02:00
|
|
|
};
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
const manageRoles = () => {
|
|
|
|
|
console.log('Manage user roles');
|
|
|
|
|
// TODO: Implement role management
|
2025-08-07 12:28:41 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const systemMaintenance = () => {
|
|
|
|
|
console.log('System maintenance');
|
2025-08-07 16:20:05 +02:00
|
|
|
// TODO: Implement maintenance mode
|
2025-08-07 12:28:41 +02:00
|
|
|
};
|
2025-08-07 16:20:05 +02:00
|
|
|
|
2025-08-10 23:29:48 +02:00
|
|
|
// 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) => {
|
2025-08-12 04:25:35 +02:00
|
|
|
// 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;
|
2025-08-10 23:29:48 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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}`);
|
|
|
|
|
|
2025-08-12 04:25:35 +02:00
|
|
|
// Close edit dialog
|
|
|
|
|
showEditDialog.value = false;
|
|
|
|
|
|
2025-08-10 23:29:48 +02:00
|
|
|
// Trigger dues refresh
|
|
|
|
|
duesRefreshTrigger.value += 1;
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-13 23:03:49 +02:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-10 23:29:48 +02:00
|
|
|
// Load stats and overdue count on component mount
|
2025-08-07 16:20:05 +02:00
|
|
|
onMounted(async () => {
|
|
|
|
|
await loadStats();
|
2025-08-10 23:29:48 +02:00
|
|
|
await loadOverdueCount();
|
2025-08-07 16:20:05 +02:00
|
|
|
});
|
2025-08-07 12:28:41 +02:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.v-card {
|
|
|
|
|
border-radius: 12px !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.v-card:hover {
|
2025-08-07 16:20:05 +02:00
|
|
|
transform: translateY(-2px);
|
|
|
|
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15) !important;
|
|
|
|
|
transition: all 0.3s ease;
|
2025-08-07 12:28:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.v-btn {
|
|
|
|
|
text-transform: none !important;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
.v-list-item {
|
|
|
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
2025-08-07 12:28:41 +02:00
|
|
|
}
|
|
|
|
|
|
2025-08-07 16:20:05 +02:00
|
|
|
.v-list-item:last-child {
|
|
|
|
|
border-bottom: none;
|
2025-08-07 12:28:41 +02:00
|
|
|
}
|
|
|
|
|
</style>
|