325 lines
10 KiB
Vue
325 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'] ? '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-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;
|
|
}
|
|
|
|
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>
|