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

524 lines
15 KiB
Vue

<template>
<v-container fluid>
<!-- Header -->
<v-row class="mb-6">
<v-col>
<h1 class="text-h3 font-weight-bold mb-2">Payment Management</h1>
<p class="text-body-1 text-medium-emphasis">Track and manage all payments and transactions</p>
</v-col>
<v-col cols="auto">
<v-btn
color="primary"
variant="flat"
prepend-icon="mdi-cash-plus"
@click="showRecordPaymentDialog = true"
>
Record Payment
</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.totalRevenue.toLocaleString() }}</div>
<div class="text-body-2 text-medium-emphasis">Total Revenue</div>
</div>
<v-icon size="32" color="success">mdi-cash</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.pending.toLocaleString() }}</div>
<div class="text-body-2 text-medium-emphasis">Pending</div>
</div>
<v-icon size="32" color="warning">mdi-clock-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.overdue.toLocaleString() }}</div>
<div class="text-body-2 text-medium-emphasis">Overdue</div>
</div>
<v-icon size="32" color="error">mdi-alert-circle-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.transactions }}</div>
<div class="text-body-2 text-medium-emphasis">Transactions</div>
</div>
<v-icon size="32" color="info">mdi-swap-horizontal</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 payments"
prepend-inner-icon="mdi-magnify"
variant="outlined"
density="compact"
clearable
hide-details
/>
</v-col>
<v-col cols="12" md="2">
<v-select
v-model="statusFilter"
label="Status"
:items="statusOptions"
variant="outlined"
density="compact"
clearable
hide-details
/>
</v-col>
<v-col cols="12" md="2">
<v-select
v-model="typeFilter"
label="Type"
:items="typeOptions"
variant="outlined"
density="compact"
clearable
hide-details
/>
</v-col>
<v-col cols="12" md="2">
<v-text-field
v-model="dateFrom"
label="From Date"
type="date"
variant="outlined"
density="compact"
hide-details
/>
</v-col>
<v-col cols="12" md="2">
<v-text-field
v-model="dateTo"
label="To Date"
type="date"
variant="outlined"
density="compact"
hide-details
/>
</v-col>
<v-col cols="12" md="1">
<v-btn
variant="outlined"
color="primary"
block
@click="exportPayments"
>
Export
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- Payments Table -->
<v-card elevation="2">
<v-data-table
:headers="headers"
:items="filteredPayments"
:search="searchQuery"
:loading="loading"
class="elevation-0"
hover
:items-per-page="10"
>
<template v-slot:item.transaction_id="{ item }">
<code class="text-caption">{{ item.transaction_id }}</code>
</template>
<template v-slot:item.member="{ item }">
<div class="py-2">
<div class="font-weight-medium">{{ item.member_name }}</div>
<div class="text-caption text-medium-emphasis">{{ item.member_email }}</div>
</div>
</template>
<template v-slot:item.amount="{ item }">
<span class="font-weight-medium">${{ item.amount.toFixed(2) }}</span>
</template>
<template v-slot:item.type="{ item }">
<v-chip
:color="getTypeColor(item.type)"
size="small"
variant="tonal"
>
{{ item.type }}
</v-chip>
</template>
<template v-slot:item.status="{ item }">
<v-chip
:color="getStatusColor(item.status)"
size="small"
variant="tonal"
>
{{ item.status }}
</v-chip>
</template>
<template v-slot:item.date="{ item }">
<span class="text-body-2">{{ formatDate(item.date) }}</span>
</template>
<template v-slot:item.actions="{ item }">
<v-btn
icon="mdi-eye"
size="small"
variant="text"
@click="viewPayment(item)"
/>
<v-btn
icon="mdi-dots-vertical"
size="small"
variant="text"
>
<v-menu activator="parent">
<v-list density="compact">
<v-list-item @click="viewReceipt(item)">
<v-list-item-title>
<v-icon size="small" class="mr-2">mdi-receipt</v-icon>
View Receipt
</v-list-item-title>
</v-list-item>
<v-list-item @click="sendReceipt(item)">
<v-list-item-title>
<v-icon size="small" class="mr-2">mdi-email</v-icon>
Email Receipt
</v-list-item-title>
</v-list-item>
<v-list-item
@click="refundPayment(item)"
:disabled="item.status !== 'Completed'"
>
<v-list-item-title>
<v-icon size="small" class="mr-2">mdi-cash-refund</v-icon>
Issue Refund
</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item
@click="markAsPaid(item)"
:disabled="item.status === 'Completed'"
class="text-success"
>
<v-list-item-title>
<v-icon size="small" class="mr-2">mdi-check</v-icon>
Mark as Paid
</v-list-item-title>
</v-list-item>
<v-list-item
@click="voidPayment(item)"
class="text-error"
:disabled="item.status === 'Voided'"
>
<v-list-item-title>
<v-icon size="small" class="mr-2">mdi-cancel</v-icon>
Void Payment
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
</template>
</v-data-table>
</v-card>
<!-- Record Payment Dialog -->
<v-dialog v-model="showRecordPaymentDialog" max-width="600">
<v-card>
<v-card-title>Record Payment</v-card-title>
<v-card-text>
<v-form ref="paymentForm">
<v-row>
<v-col cols="12">
<v-autocomplete
v-model="paymentForm.member_id"
label="Member"
:items="membersList"
item-title="name"
item-value="id"
variant="outlined"
required
/>
</v-col>
<v-col cols="12" md="6">
<v-select
v-model="paymentForm.type"
label="Payment Type"
:items="typeOptions"
variant="outlined"
required
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="paymentForm.amount"
label="Amount"
prefix="$"
type="number"
variant="outlined"
required
/>
</v-col>
<v-col cols="12" md="6">
<v-select
v-model="paymentForm.method"
label="Payment Method"
:items="['Credit Card', 'Check', 'Cash', 'Bank Transfer']"
variant="outlined"
required
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="paymentForm.reference"
label="Reference Number"
variant="outlined"
/>
</v-col>
<v-col cols="12">
<v-textarea
v-model="paymentForm.notes"
label="Notes"
variant="outlined"
rows="2"
/>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="showRecordPaymentDialog = false">Cancel</v-btn>
<v-btn color="primary" variant="flat" @click="savePayment">Record</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'admin',
middleware: 'admin'
});
// State
const loading = ref(false);
const showRecordPaymentDialog = ref(false);
const searchQuery = ref('');
const statusFilter = ref(null);
const typeFilter = ref(null);
const dateFrom = ref('');
const dateTo = ref('');
// Stats
const stats = ref({
totalRevenue: 45820,
pending: 3250,
overdue: 1800,
transactions: 342
});
// Form data
const paymentForm = ref({
member_id: '',
type: '',
amount: 0,
method: '',
reference: '',
notes: ''
});
// Options
const statusOptions = ['Completed', 'Pending', 'Failed', 'Refunded', 'Voided'];
const typeOptions = ['Membership', 'Event', 'Donation', 'Other'];
// Mock members list
const membersList = [
{ id: '1', name: 'John Smith' },
{ id: '2', name: 'Sarah Johnson' },
{ id: '3', name: 'Michael Williams' }
];
// Table configuration
const headers = [
{ title: 'Transaction ID', key: 'transaction_id', sortable: true },
{ title: 'Member', key: 'member', sortable: true },
{ title: 'Amount', key: 'amount', sortable: true },
{ title: 'Type', key: 'type', sortable: true },
{ title: 'Status', key: 'status', sortable: true },
{ title: 'Date', key: 'date', sortable: true },
{ title: 'Actions', key: 'actions', sortable: false, align: 'end' }
];
// Mock data
const payments = ref([
{
id: 1,
transaction_id: 'TXN-2024-001',
member_name: 'John Smith',
member_email: 'john.smith@example.com',
amount: 500,
type: 'Membership',
status: 'Completed',
date: new Date('2024-01-15'),
method: 'Credit Card'
},
{
id: 2,
transaction_id: 'TXN-2024-002',
member_name: 'Sarah Johnson',
member_email: 'sarah.j@example.com',
amount: 250,
type: 'Event',
status: 'Pending',
date: new Date('2024-01-14'),
method: 'Bank Transfer'
},
{
id: 3,
transaction_id: 'TXN-2024-003',
member_name: 'Michael Williams',
member_email: 'michael.w@example.com',
amount: 1000,
type: 'Donation',
status: 'Completed',
date: new Date('2024-01-13'),
method: 'Check'
},
{
id: 4,
transaction_id: 'TXN-2024-004',
member_name: 'Emma Davis',
member_email: 'emma.d@example.com',
amount: 75,
type: 'Event',
status: 'Failed',
date: new Date('2024-01-12'),
method: 'Credit Card'
}
]);
// Computed
const filteredPayments = computed(() => {
let filtered = [...payments.value];
if (statusFilter.value) {
filtered = filtered.filter(p => p.status === statusFilter.value);
}
if (typeFilter.value) {
filtered = filtered.filter(p => p.type === typeFilter.value);
}
if (dateFrom.value) {
const from = new Date(dateFrom.value);
filtered = filtered.filter(p => new Date(p.date) >= from);
}
if (dateTo.value) {
const to = new Date(dateTo.value);
filtered = filtered.filter(p => new Date(p.date) <= to);
}
return filtered;
});
// Methods
const getStatusColor = (status: string) => {
switch (status) {
case 'Completed': return 'success';
case 'Pending': return 'warning';
case 'Failed': return 'error';
case 'Refunded': return 'info';
case 'Voided': return 'default';
default: return 'default';
}
};
const getTypeColor = (type: string) => {
switch (type) {
case 'Membership': return 'primary';
case 'Event': return 'info';
case 'Donation': return 'success';
default: return 'default';
}
};
const formatDate = (date: Date) => {
return new Date(date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
};
const viewPayment = (payment: any) => {
console.log('View payment:', payment);
};
const viewReceipt = (payment: any) => {
console.log('View receipt:', payment);
};
const sendReceipt = (payment: any) => {
console.log('Send receipt:', payment);
};
const refundPayment = (payment: any) => {
console.log('Refund payment:', payment);
};
const markAsPaid = (payment: any) => {
payment.status = 'Completed';
};
const voidPayment = (payment: any) => {
payment.status = 'Voided';
};
const exportPayments = () => {
console.log('Export payments');
};
const savePayment = () => {
console.log('Save payment:', paymentForm.value);
showRecordPaymentDialog.value = false;
};
</script>