monacousa-portal/pages/admin/members/index.vue

507 lines
14 KiB
Vue

<template>
<v-container fluid>
<!-- Header -->
<v-row class="mb-6">
<v-col>
<h1 class="text-h3 font-weight-bold mb-2">Member Management</h1>
<p class="text-body-1 text-medium-emphasis">Manage association members and their information</p>
</v-col>
<v-col cols="auto">
<v-btn
color="primary"
variant="flat"
prepend-icon="mdi-account-plus"
@click="showCreateDialog = true"
>
Add Member
</v-btn>
</v-col>
</v-row>
<!-- Stats Cards -->
<v-row class="mb-6">
<v-col cols="12" md="3">
<v-card elevation="2">
<v-card-text>
<div class="d-flex align-center justify-space-between">
<div>
<div class="text-h4 font-weight-bold">{{ stats.total }}</div>
<div class="text-body-2 text-medium-emphasis">Total Members</div>
</div>
<v-icon size="32" color="primary">mdi-account-group</v-icon>
</div>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="3">
<v-card elevation="2">
<v-card-text>
<div class="d-flex align-center justify-space-between">
<div>
<div class="text-h4 font-weight-bold">{{ stats.active }}</div>
<div class="text-body-2 text-medium-emphasis">Active Members</div>
</div>
<v-icon size="32" color="success">mdi-account-check</v-icon>
</div>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="3">
<v-card elevation="2">
<v-card-text>
<div class="d-flex align-center justify-space-between">
<div>
<div class="text-h4 font-weight-bold">{{ stats.newThisMonth }}</div>
<div class="text-body-2 text-medium-emphasis">New This Month</div>
</div>
<v-icon size="32" color="info">mdi-account-plus-outline</v-icon>
</div>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" md="3">
<v-card elevation="2">
<v-card-text>
<div class="d-flex align-center justify-space-between">
<div>
<div class="text-h4 font-weight-bold">{{ stats.renewalDue }}</div>
<div class="text-body-2 text-medium-emphasis">Renewal Due</div>
</div>
<v-icon size="32" color="warning">mdi-clock-alert-outline</v-icon>
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
<!-- Filters -->
<v-card class="mb-6" elevation="0">
<v-card-text>
<v-row>
<v-col cols="12" md="3">
<v-text-field
v-model="searchQuery"
label="Search members"
prepend-inner-icon="mdi-magnify"
variant="outlined"
density="compact"
clearable
hide-details
/>
</v-col>
<v-col cols="12" md="3">
<v-select
v-model="statusFilter"
label="Status"
:items="statusOptions"
variant="outlined"
density="compact"
clearable
hide-details
/>
</v-col>
<v-col cols="12" md="3">
<v-select
v-model="membershipFilter"
label="Membership Type"
:items="membershipOptions"
variant="outlined"
density="compact"
clearable
hide-details
/>
</v-col>
<v-col cols="12" md="3">
<v-btn
variant="outlined"
color="primary"
block
@click="exportMembers"
>
<v-icon start>mdi-download</v-icon>
Export List
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- Members Table -->
<v-card elevation="2">
<v-data-table
:headers="headers"
:items="filteredMembers"
:search="searchQuery"
:loading="loading"
class="elevation-0"
hover
:items-per-page="10"
>
<template v-slot:item.name="{ item }">
<div class="d-flex align-center py-2">
<ProfileAvatar
:member-id="item.member_id"
:first-name="item.first_name"
:last-name="item.last_name"
size="40"
class="mr-3"
/>
<div>
<div class="font-weight-medium">{{ item.first_name }} {{ item.last_name }}</div>
<div class="text-caption text-medium-emphasis">{{ item.email }}</div>
</div>
</div>
</template>
<template v-slot:item.membership="{ item }">
<v-chip
:color="getMembershipColor(item.membership_type)"
size="small"
variant="tonal"
>
{{ item.membership_type }}
</v-chip>
</template>
<template v-slot:item.status="{ item }">
<v-chip
:color="item.status === 'active' ? 'success' : 'error'"
size="small"
variant="tonal"
>
{{ item.status }}
</v-chip>
</template>
<template v-slot:item.dues_status="{ item }">
<v-chip
:color="getDuesColor(item.dues_status)"
size="small"
variant="tonal"
>
{{ item.dues_status }}
</v-chip>
</template>
<template v-slot:item.join_date="{ item }">
<span class="text-body-2">{{ formatDate(item.join_date) }}</span>
</template>
<template v-slot:item.actions="{ item }">
<v-btn
icon="mdi-eye"
size="small"
variant="text"
@click="viewMember(item)"
/>
<v-btn
icon="mdi-pencil"
size="small"
variant="text"
@click="editMember(item)"
/>
<v-btn
icon="mdi-dots-vertical"
size="small"
variant="text"
>
<v-menu activator="parent">
<v-list density="compact">
<v-list-item @click="sendEmail(item)">
<v-list-item-title>
<v-icon size="small" class="mr-2">mdi-email</v-icon>
Send Email
</v-list-item-title>
</v-list-item>
<v-list-item @click="viewPaymentHistory(item)">
<v-list-item-title>
<v-icon size="small" class="mr-2">mdi-history</v-icon>
Payment History
</v-list-item-title>
</v-list-item>
<v-list-item @click="generateInvoice(item)">
<v-list-item-title>
<v-icon size="small" class="mr-2">mdi-file-document</v-icon>
Generate Invoice
</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item
@click="toggleStatus(item)"
:class="item.status === 'active' ? 'text-error' : 'text-success'"
>
<v-list-item-title>
<v-icon size="small" class="mr-2">
{{ item.status === 'active' ? 'mdi-account-off' : 'mdi-account-check' }}
</v-icon>
{{ item.status === 'active' ? 'Deactivate' : 'Activate' }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
</template>
</v-data-table>
</v-card>
<!-- View Member Dialog -->
<ViewMemberDialog
v-model="showViewDialog"
:member="selectedMember"
@edit="handleEditMember"
/>
<!-- Edit Member Dialog -->
<EditMemberDialog
v-model="showEditDialog"
:member="selectedMember"
@member-updated="handleMemberUpdated"
/>
<!-- Create Member Dialog -->
<v-dialog v-model="showCreateDialog" max-width="600">
<v-card>
<v-card-title>Add New Member</v-card-title>
<v-card-text>
<v-form ref="memberForm">
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="memberForm.first_name"
label="First Name"
variant="outlined"
required
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="memberForm.last_name"
label="Last Name"
variant="outlined"
required
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="memberForm.email"
label="Email"
variant="outlined"
type="email"
required
/>
</v-col>
<v-col cols="12" md="6">
<v-select
v-model="memberForm.membership_type"
label="Membership Type"
:items="membershipOptions"
variant="outlined"
required
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="memberForm.phone"
label="Phone"
variant="outlined"
/>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="showCreateDialog = false">Cancel</v-btn>
<v-btn color="primary" variant="flat" @click="saveMember">Create</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template>
<script setup lang="ts">
import type { Member } from '~/utils/types';
definePageMeta({
layout: 'admin',
middleware: 'admin'
});
// State
const loading = ref(false);
const showViewDialog = ref(false);
const showEditDialog = ref(false);
const showCreateDialog = ref(false);
const selectedMember = ref<Member | null>(null);
const searchQuery = ref('');
const statusFilter = ref(null);
const membershipFilter = ref(null);
// Stats
const stats = ref({
total: 156,
active: 142,
newThisMonth: 8,
renewalDue: 23
});
// Form data
const memberForm = ref({
first_name: '',
last_name: '',
email: '',
membership_type: 'Standard',
phone: ''
});
// Options
const statusOptions = ['active', 'inactive'];
const membershipOptions = ['Standard', 'Premium', 'VIP', 'Lifetime'];
// Table configuration
const headers = [
{ title: 'Member', key: 'name', sortable: true },
{ title: 'Membership', key: 'membership', sortable: true },
{ title: 'Status', key: 'status', sortable: true },
{ title: 'Dues', key: 'dues_status', sortable: true },
{ title: 'Joined', key: 'join_date', sortable: true },
{ title: 'Actions', key: 'actions', sortable: false, align: 'end' }
];
// Mock data
const members = ref<Member[]>([
{
member_id: '1',
first_name: 'John',
last_name: 'Smith',
email: 'john.smith@example.com',
membership_type: 'Premium',
status: 'active',
dues_status: 'Paid',
join_date: '2023-01-15',
phone: '555-0100'
},
{
member_id: '2',
first_name: 'Sarah',
last_name: 'Johnson',
email: 'sarah.j@example.com',
membership_type: 'Standard',
status: 'active',
dues_status: 'Due',
join_date: '2023-03-22',
phone: '555-0101'
},
{
member_id: '3',
first_name: 'Michael',
last_name: 'Williams',
email: 'michael.w@example.com',
membership_type: 'VIP',
status: 'active',
dues_status: 'Paid',
join_date: '2022-11-08',
phone: '555-0102'
}
]);
// Computed
const filteredMembers = computed(() => {
let filtered = [...members.value];
if (statusFilter.value) {
filtered = filtered.filter(m => m.status === statusFilter.value);
}
if (membershipFilter.value) {
filtered = filtered.filter(m => m.membership_type === membershipFilter.value);
}
return filtered;
});
// Methods
const getMembershipColor = (type: string) => {
switch (type) {
case 'VIP': return 'error';
case 'Premium': return 'warning';
case 'Lifetime': return 'purple';
default: return 'info';
}
};
const getDuesColor = (status: string) => {
switch (status) {
case 'Paid': return 'success';
case 'Due': return 'warning';
case 'Overdue': return 'error';
default: return 'default';
}
};
const formatDate = (date: string) => {
return new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
};
const viewMember = (member: Member) => {
selectedMember.value = member;
showViewDialog.value = true;
};
const editMember = (member: Member) => {
selectedMember.value = member;
showEditDialog.value = true;
};
const handleEditMember = (member: Member) => {
showViewDialog.value = false;
selectedMember.value = member;
showEditDialog.value = true;
};
const handleMemberUpdated = (member: Member) => {
const index = members.value.findIndex(m => m.member_id === member.member_id);
if (index > -1) {
members.value[index] = member;
}
showEditDialog.value = false;
};
const sendEmail = (member: Member) => {
console.log('Send email to:', member);
};
const viewPaymentHistory = (member: Member) => {
console.log('View payment history:', member);
};
const generateInvoice = (member: Member) => {
console.log('Generate invoice:', member);
};
const toggleStatus = (member: Member) => {
member.status = member.status === 'active' ? 'inactive' : 'active';
};
const exportMembers = () => {
console.log('Export members list');
};
const saveMember = () => {
console.log('Save member:', memberForm.value);
showCreateDialog.value = false;
};
// Load data on mount
onMounted(async () => {
loading.value = true;
// Fetch members from API
setTimeout(() => {
loading.value = false;
}, 1000);
});
</script>