monacousa-portal/components/ViewMemberDialog.vue

334 lines
10 KiB
Vue

<template>
<v-dialog
:model-value="modelValue"
@update:model-value="$emit('update:model-value', $event)"
max-width="600"
persistent
scrollable
>
<v-card v-if="member">
<!-- Header -->
<v-card-title class="d-flex align-center pa-6 bg-primary">
<v-avatar
:color="avatarColor"
size="48"
class="mr-4"
>
<span class="text-white font-weight-bold text-h6">
{{ memberInitials }}
</span>
</v-avatar>
<div class="flex-grow-1">
<h2 class="text-h5 text-white font-weight-bold">
{{ member.FullName || `${member.first_name} ${member.last_name}` }}
</h2>
<div class="d-flex align-center mt-1">
<CountryFlag
v-if="member.nationality"
:country-code="member.nationality"
:show-name="false"
size="small"
class="mr-2"
/>
<span class="text-white text-body-2">
{{ getCountryName(member.nationality) || 'Unknown Country' }}
</span>
</div>
</div>
<v-btn
icon
variant="text"
color="white"
@click="$emit('update:model-value', false)"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<!-- Status Chips -->
<v-card-text class="py-4">
<div class="d-flex flex-wrap gap-2 mb-4">
<v-chip
:color="statusColor"
variant="flat"
size="small"
>
<v-icon start size="16">{{ statusIcon }}</v-icon>
{{ member.membership_status }}
</v-chip>
<v-chip
:color="duesColor"
:variant="duesVariant"
size="small"
>
<v-icon start size="16">{{ duesIcon }}</v-icon>
{{ duesText }}
</v-chip>
<v-chip
v-if="member.payment_due_date"
:color="isOverdue ? 'error' : 'warning'"
variant="tonal"
size="small"
>
<v-icon start size="16">mdi-calendar-alert</v-icon>
{{ isOverdue ? 'Payment Overdue' : 'Payment Due' }}
</v-chip>
</div>
<!-- Member Information -->
<v-row>
<!-- Personal Information -->
<v-col cols="12" md="6">
<h3 class="text-h6 mb-3 text-primary">Personal Information</h3>
<div class="info-group">
<div class="info-item mb-3">
<label class="text-body-2 font-weight-bold text-medium-emphasis">First Name</label>
<p class="text-body-1 ma-0">{{ member.first_name || 'Not provided' }}</p>
</div>
<div class="info-item mb-3">
<label class="text-body-2 font-weight-bold text-medium-emphasis">Last Name</label>
<p class="text-body-1 ma-0">{{ member.last_name || 'Not provided' }}</p>
</div>
<div class="info-item mb-3">
<label class="text-body-2 font-weight-bold text-medium-emphasis">Email</label>
<p class="text-body-1 ma-0">
<a v-if="member.email" :href="`mailto:${member.email}`" class="text-primary">
{{ member.email }}
</a>
<span v-else>Not provided</span>
</p>
</div>
<div class="info-item mb-3" v-if="member.phone">
<label class="text-body-2 font-weight-bold text-medium-emphasis">Phone</label>
<p class="text-body-1 ma-0">
<a :href="`tel:${member.phone}`" class="text-primary">
{{ member.FormattedPhone || member.phone }}
</a>
</p>
</div>
<div class="info-item mb-3" v-if="member.date_of_birth">
<label class="text-body-2 font-weight-bold text-medium-emphasis">Date of Birth</label>
<p class="text-body-1 ma-0">{{ formatDate(member.date_of_birth) }}</p>
</div>
<div class="info-item mb-3" v-if="member.address">
<label class="text-body-2 font-weight-bold text-medium-emphasis">Address</label>
<p class="text-body-1 ma-0">{{ member.address }}</p>
</div>
</div>
</v-col>
<!-- Membership Information -->
<v-col cols="12" md="6">
<h3 class="text-h6 mb-3 text-primary">Membership Information</h3>
<div class="info-group">
<div class="info-item mb-3">
<label class="text-body-2 font-weight-bold text-medium-emphasis">Member Since</label>
<p class="text-body-1 ma-0">{{ formatDate(member.member_since) || 'Not specified' }}</p>
</div>
<div class="info-item mb-3">
<label class="text-body-2 font-weight-bold text-medium-emphasis">Membership Status</label>
<p class="text-body-1 ma-0">
<v-chip :color="statusColor" size="small" variant="tonal">
{{ member.membership_status }}
</v-chip>
</p>
</div>
<div class="info-item mb-3">
<label class="text-body-2 font-weight-bold text-medium-emphasis">Current Year Dues</label>
<p class="text-body-1 ma-0">
<v-chip :color="duesColor" size="small" variant="tonal">
{{ member.current_year_dues_paid === 'true' ? 'Paid' : 'Outstanding' }}
</v-chip>
</p>
</div>
<div class="info-item mb-3" v-if="member.membership_date_paid">
<label class="text-body-2 font-weight-bold text-medium-emphasis">Last Payment Date</label>
<p class="text-body-1 ma-0">{{ formatDate(member.membership_date_paid) }}</p>
</div>
<div class="info-item mb-3" v-if="member.payment_due_date">
<label class="text-body-2 font-weight-bold text-medium-emphasis">Payment Due Date</label>
<p class="text-body-1 ma-0" :class="{ 'text-error': isOverdue }">
{{ formatDate(member.payment_due_date) }}
<span v-if="isOverdue" class="text-error font-weight-bold"> (Overdue)</span>
</p>
</div>
</div>
</v-col>
</v-row>
</v-card-text>
<!-- Actions -->
<v-card-actions class="pa-6 pt-0">
<v-spacer />
<v-btn
variant="text"
@click="$emit('update:model-value', false)"
>
Close
</v-btn>
<v-btn
color="primary"
variant="elevated"
@click="$emit('edit', member)"
>
<v-icon start>mdi-pencil</v-icon>
Edit
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import type { Member } from '~/utils/types';
import { getCountryName } from '~/utils/countries';
interface Props {
modelValue: boolean;
member?: Member | null;
}
interface Emits {
(e: 'update:model-value', value: boolean): void;
(e: 'edit', member: Member): void;
}
const props = withDefaults(defineProps<Props>(), {
member: null
});
defineEmits<Emits>();
// Computed properties
const memberInitials = computed(() => {
if (!props.member) return '';
const firstName = props.member.first_name || '';
const lastName = props.member.last_name || '';
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
});
const avatarColor = computed(() => {
if (!props.member) return 'grey';
const colors = ['primary', 'secondary', 'accent', 'info', 'warning', 'success'];
const idNumber = parseInt(props.member.Id) || 0;
return colors[idNumber % colors.length];
});
const statusColor = computed(() => {
if (!props.member) return 'grey';
const status = props.member.membership_status;
switch (status) {
case 'Active': return 'success';
case 'Inactive': return 'grey';
case 'Pending': return 'warning';
case 'Expired': return 'error';
default: return 'grey';
}
});
const statusIcon = computed(() => {
if (!props.member) return 'mdi-help';
const status = props.member.membership_status;
switch (status) {
case 'Active': return 'mdi-check-circle';
case 'Inactive': return 'mdi-pause-circle';
case 'Pending': return 'mdi-clock';
case 'Expired': return 'mdi-alert-circle';
default: return 'mdi-help';
}
});
const duesColor = computed(() => {
if (!props.member) return 'grey';
return props.member.current_year_dues_paid === 'true' ? 'success' : 'error';
});
const duesVariant = computed(() => {
if (!props.member) return 'tonal';
return props.member.current_year_dues_paid === 'true' ? 'tonal' : 'flat';
});
const duesIcon = computed(() => {
if (!props.member) return 'mdi-help';
return props.member.current_year_dues_paid === 'true' ? 'mdi-check-circle' : 'mdi-alert-circle';
});
const duesText = computed(() => {
if (!props.member) return '';
return props.member.current_year_dues_paid === 'true' ? 'Dues Paid' : 'Dues Outstanding';
});
const isOverdue = computed(() => {
if (!props.member || !props.member.payment_due_date) return false;
const dueDate = new Date(props.member.payment_due_date);
const today = new Date();
return dueDate < today && props.member.current_year_dues_paid !== 'true';
});
// Methods
const formatDate = (dateString: string): string => {
if (!dateString) return '';
try {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
} catch {
return dateString;
}
};
</script>
<style scoped>
.info-group {
background: rgba(var(--v-theme-surface-variant), 0.1);
border-radius: 8px;
padding: 16px;
}
.info-item {
border-bottom: 1px solid rgba(var(--v-theme-outline), 0.12);
padding-bottom: 8px;
}
.info-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.info-item label {
display: block;
margin-bottom: 4px;
}
.bg-primary {
background: linear-gradient(135deg, #a31515 0%, #d32f2f 100%) !important;
}
.text-error {
color: rgb(var(--v-theme-error)) !important;
}
.text-primary {
color: #a31515 !important;
}
</style>