298 lines
8.0 KiB
Vue
298 lines
8.0 KiB
Vue
<template>
|
|
<v-card
|
|
:class="[
|
|
'dues-action-card',
|
|
status === 'overdue' ? 'dues-action-card--overdue' : 'dues-action-card--upcoming'
|
|
]"
|
|
elevation="2"
|
|
>
|
|
<!-- Status Badge -->
|
|
<div class="status-badge">
|
|
<v-chip
|
|
:color="statusColor"
|
|
size="small"
|
|
variant="flat"
|
|
>
|
|
<v-icon start size="12">{{ statusIcon }}</v-icon>
|
|
{{ statusText }}
|
|
</v-chip>
|
|
</div>
|
|
|
|
<v-card-text class="pa-4">
|
|
<!-- Member Info Header -->
|
|
<div class="d-flex align-center mb-3">
|
|
<v-avatar
|
|
:color="avatarColor"
|
|
size="40"
|
|
class="mr-3"
|
|
>
|
|
<span class="text-white font-weight-bold">
|
|
{{ memberInitials }}
|
|
</span>
|
|
</v-avatar>
|
|
|
|
<div class="flex-grow-1">
|
|
<h4 class="text-subtitle-1 font-weight-bold mb-1">
|
|
{{ member.FullName || `${member.first_name} ${member.last_name}` }}
|
|
</h4>
|
|
<div class="d-flex align-center">
|
|
<v-chip size="x-small" color="grey" variant="text" class="pa-0 mr-2">
|
|
ID: {{ member.member_id || `MUSA-${member.Id}` }}
|
|
</v-chip>
|
|
<CountryFlag
|
|
v-if="member.nationality"
|
|
:country-code="member.nationality.split(',')[0]"
|
|
:show-name="false"
|
|
size="small"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dues Information -->
|
|
<div class="dues-info mb-3">
|
|
<div v-if="status === 'overdue'">
|
|
<!-- Overdue Information -->
|
|
<div class="d-flex justify-space-between align-center mb-2">
|
|
<span class="text-body-2 text-medium-emphasis">
|
|
<v-icon size="14" class="mr-1">mdi-clock-alert</v-icon>
|
|
Days Overdue
|
|
</span>
|
|
<span class="text-body-2 font-weight-bold text-error">
|
|
{{ member.overdueDays || 0 }} days
|
|
</span>
|
|
</div>
|
|
|
|
<div v-if="member.overdueReason" class="overdue-reason">
|
|
<span class="text-caption text-error">
|
|
<v-icon size="12" class="mr-1">mdi-information</v-icon>
|
|
{{ member.overdueReason }}
|
|
</span>
|
|
</div>
|
|
|
|
<div v-if="member.membership_date_paid" class="d-flex justify-space-between align-center mt-2">
|
|
<span class="text-body-2 text-medium-emphasis">
|
|
<v-icon size="14" class="mr-1">mdi-calendar-check</v-icon>
|
|
Last Payment
|
|
</span>
|
|
<span class="text-body-2">
|
|
{{ formatDate(member.membership_date_paid) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<!-- Upcoming Information -->
|
|
<div class="d-flex justify-space-between align-center mb-2">
|
|
<span class="text-body-2 text-medium-emphasis">
|
|
<v-icon size="14" class="mr-1">mdi-calendar</v-icon>
|
|
Due Date
|
|
</span>
|
|
<span class="text-body-2 font-weight-bold text-warning">
|
|
{{ formatDate(member.nextDueDate || member.payment_due_date) }}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="d-flex justify-space-between align-center">
|
|
<span class="text-body-2 text-medium-emphasis">
|
|
<v-icon size="14" class="mr-1">mdi-clock</v-icon>
|
|
Days Until Due
|
|
</span>
|
|
<span class="text-body-2 font-weight-bold text-warning">
|
|
{{ member.daysUntilDue || 0 }} days
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contact Info -->
|
|
<div class="contact-info mb-3">
|
|
<div v-if="member.email" class="d-flex align-center mb-1">
|
|
<v-icon size="14" class="mr-2 text-medium-emphasis">mdi-email</v-icon>
|
|
<span class="text-body-2 text-truncate">{{ member.email }}</span>
|
|
</div>
|
|
<div v-if="member.phone" class="d-flex align-center">
|
|
<v-icon size="14" class="mr-2 text-medium-emphasis">mdi-phone</v-icon>
|
|
<span class="text-body-2">{{ member.FormattedPhone || member.phone }}</span>
|
|
</div>
|
|
</div>
|
|
</v-card-text>
|
|
|
|
<!-- Action Buttons -->
|
|
<v-card-actions class="pa-4 pt-0">
|
|
<v-btn
|
|
color="success"
|
|
variant="elevated"
|
|
size="small"
|
|
:loading="loading"
|
|
@click="$emit('mark-paid', member)"
|
|
block
|
|
>
|
|
<v-icon start size="16">mdi-check-circle</v-icon>
|
|
Mark as Paid
|
|
</v-btn>
|
|
</v-card-actions>
|
|
|
|
<!-- Quick Actions -->
|
|
<v-card-actions class="pa-4 pt-0">
|
|
<v-btn
|
|
variant="text"
|
|
size="small"
|
|
@click="$emit('view-member', member)"
|
|
>
|
|
<v-icon start size="16">mdi-account</v-icon>
|
|
View Details
|
|
</v-btn>
|
|
|
|
<v-spacer />
|
|
|
|
<v-btn
|
|
variant="text"
|
|
size="small"
|
|
:href="`mailto:${member.email}?subject=MonacoUSA Membership Dues Reminder`"
|
|
target="_blank"
|
|
v-if="member.email"
|
|
>
|
|
<v-icon start size="16">mdi-email</v-icon>
|
|
Email
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Member } from '~/utils/types';
|
|
|
|
// Extended member type for dues management
|
|
interface DuesMember extends Member {
|
|
overdueDays?: number;
|
|
overdueReason?: string;
|
|
daysUntilDue?: number;
|
|
nextDueDate?: string;
|
|
}
|
|
|
|
interface Props {
|
|
member: DuesMember;
|
|
status: 'overdue' | 'upcoming';
|
|
loading?: boolean;
|
|
}
|
|
|
|
interface Emits {
|
|
(e: 'mark-paid', member: Member): void;
|
|
(e: 'view-member', member: Member): void;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
loading: false
|
|
});
|
|
|
|
defineEmits<Emits>();
|
|
|
|
// Computed properties
|
|
const memberInitials = computed(() => {
|
|
const firstName = props.member.first_name || '';
|
|
const lastName = props.member.last_name || '';
|
|
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
|
|
});
|
|
|
|
const avatarColor = computed(() => {
|
|
const colors = ['red', 'blue', 'green', 'orange', 'purple', 'teal', 'indigo', 'pink'];
|
|
const idNumber = parseInt(props.member.Id) || 0;
|
|
return colors[idNumber % colors.length];
|
|
});
|
|
|
|
const statusColor = computed(() => {
|
|
return props.status === 'overdue' ? 'error' : 'warning';
|
|
});
|
|
|
|
const statusIcon = computed(() => {
|
|
return props.status === 'overdue' ? 'mdi-alert-circle' : 'mdi-clock-alert';
|
|
});
|
|
|
|
const statusText = computed(() => {
|
|
return props.status === 'overdue' ? 'Overdue' : 'Due Soon';
|
|
});
|
|
|
|
const daysDifference = computed(() => {
|
|
if (!props.member.payment_due_date) return null;
|
|
|
|
const today = new Date();
|
|
const dueDate = new Date(props.member.payment_due_date);
|
|
const diffTime = dueDate.getTime() - today.getTime();
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
|
return diffDays;
|
|
});
|
|
|
|
// Methods
|
|
const formatDate = (dateString: string): string => {
|
|
if (!dateString) return '';
|
|
|
|
try {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
});
|
|
} catch {
|
|
return dateString;
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.dues-action-card {
|
|
border-radius: 12px !important;
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
height: 100%;
|
|
}
|
|
|
|
.dues-action-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
|
}
|
|
|
|
.dues-action-card--overdue {
|
|
border-left: 4px solid rgb(var(--v-theme-error));
|
|
}
|
|
|
|
.dues-action-card--upcoming {
|
|
border-left: 4px solid rgb(var(--v-theme-warning));
|
|
}
|
|
|
|
.status-badge {
|
|
position: absolute;
|
|
top: 12px;
|
|
right: 12px;
|
|
z-index: 2;
|
|
}
|
|
|
|
.dues-info {
|
|
background: rgba(var(--v-theme-surface-variant), 0.1);
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
}
|
|
|
|
.contact-info {
|
|
border-radius: 6px;
|
|
padding: 8px;
|
|
background: rgba(var(--v-theme-surface-variant), 0.05);
|
|
}
|
|
|
|
.text-truncate {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 150px;
|
|
}
|
|
|
|
/* Mobile responsive */
|
|
@media (max-width: 600px) {
|
|
.dues-action-card {
|
|
margin-bottom: 12px;
|
|
}
|
|
}
|
|
</style>
|