334 lines
10 KiB
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>
|