implemented comprehensive member card enhancements with complete multiple nationality support and dues management features.
Build And Push Image / docker (push) Successful in 2m54s
Details
Build And Push Image / docker (push) Successful in 2m54s
Details
This commit is contained in:
parent
9202509c9c
commit
f6bc81cb01
|
|
@ -11,13 +11,42 @@
|
|||
:color="statusColor"
|
||||
size="small"
|
||||
variant="flat"
|
||||
class="font-weight-bold"
|
||||
>
|
||||
{{ member.membership_status }}
|
||||
<v-icon v-if="!isActive" start size="12">mdi-account-off</v-icon>
|
||||
<v-icon v-else start size="12">mdi-account-check</v-icon>
|
||||
{{ member.membership_status || 'Inactive' }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- Card Header -->
|
||||
<v-card-text class="pb-2">
|
||||
<!-- Action Buttons -->
|
||||
<div v-if="canEdit || canDelete" class="member-action-buttons">
|
||||
<v-btn
|
||||
v-if="canEdit"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
@click.stop="$emit('edit', member)"
|
||||
:title="'Edit ' + member.FullName"
|
||||
>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
v-if="canDelete"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
color="error"
|
||||
@click.stop="$emit('delete', member)"
|
||||
:title="'Delete ' + member.FullName"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- Card Content -->
|
||||
<v-card-text class="pb-4">
|
||||
<div class="d-flex align-center mb-3">
|
||||
<v-avatar
|
||||
:color="avatarColor"
|
||||
|
|
@ -33,17 +62,33 @@
|
|||
<h3 class="text-h6 font-weight-bold mb-1">
|
||||
{{ member.FullName || `${member.first_name} ${member.last_name}` }}
|
||||
</h3>
|
||||
<div class="d-flex align-center">
|
||||
<CountryFlag
|
||||
v-if="member.nationality"
|
||||
:country-code="member.nationality"
|
||||
:show-name="false"
|
||||
size="small"
|
||||
class="mr-2"
|
||||
/>
|
||||
<span class="text-body-2 text-medium-emphasis">
|
||||
{{ getCountryName(member.nationality) || 'Unknown' }}
|
||||
</span>
|
||||
<div class="nationality-display">
|
||||
<template v-if="nationalitiesArray.length > 0">
|
||||
<div class="d-flex align-center flex-wrap">
|
||||
<!-- Display all flags together -->
|
||||
<div class="flags-container d-flex align-center me-2">
|
||||
<CountryFlag
|
||||
v-for="nationality in nationalitiesArray"
|
||||
:key="nationality"
|
||||
:country-code="nationality"
|
||||
:show-name="false"
|
||||
size="small"
|
||||
class="flag-item"
|
||||
/>
|
||||
</div>
|
||||
<!-- Display country names -->
|
||||
<div class="country-names">
|
||||
<span class="text-body-2 text-medium-emphasis">
|
||||
{{ nationalitiesArray.map(n => getCountryName(n)).join(', ') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="text-body-2 text-medium-emphasis">
|
||||
Unknown
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -78,8 +123,20 @@
|
|||
{{ duesText }}
|
||||
</v-chip>
|
||||
|
||||
<!-- Dues Coming Due Warning -->
|
||||
<v-chip
|
||||
v-if="member.payment_due_date"
|
||||
v-if="isDuesComingDue"
|
||||
color="orange"
|
||||
variant="flat"
|
||||
size="small"
|
||||
class="mr-2"
|
||||
>
|
||||
<v-icon start size="14">mdi-clock-alert</v-icon>
|
||||
Due {{ formatDate(nextDuesDate) }}
|
||||
</v-chip>
|
||||
|
||||
<v-chip
|
||||
v-if="member.payment_due_date && !isDuesComingDue"
|
||||
color="warning"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
|
|
@ -91,34 +148,6 @@
|
|||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<!-- Card Actions -->
|
||||
<v-card-actions v-if="canEdit || canDelete" class="pt-0">
|
||||
<v-spacer />
|
||||
|
||||
<v-btn
|
||||
v-if="canEdit"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
@click.stop="$emit('edit', member)"
|
||||
:title="'Edit ' + member.FullName"
|
||||
>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
v-if="canDelete"
|
||||
icon
|
||||
size="small"
|
||||
variant="text"
|
||||
color="error"
|
||||
@click.stop="$emit('delete', member)"
|
||||
:title="'Delete ' + member.FullName"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
<!-- Click overlay for better UX -->
|
||||
<div class="member-card-overlay" @click="$emit('view', member)"></div>
|
||||
</v-card>
|
||||
|
|
@ -148,11 +177,11 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
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 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(() => {
|
||||
// Generate consistent color based on member ID using high-contrast colors
|
||||
|
|
@ -161,6 +190,18 @@ const avatarColor = computed(() => {
|
|||
return colors[idNumber % colors.length];
|
||||
});
|
||||
|
||||
const nationalitiesArray = computed(() => {
|
||||
if (!props.member.nationality) return [];
|
||||
|
||||
// Handle multiple nationalities separated by comma, semicolon, or pipe
|
||||
const nationalities = props.member.nationality
|
||||
.split(/[,;|]/)
|
||||
.map(n => n.trim().toUpperCase())
|
||||
.filter(n => n.length > 0);
|
||||
|
||||
return nationalities;
|
||||
});
|
||||
|
||||
const isActive = computed(() => {
|
||||
return props.member.membership_status === 'Active';
|
||||
});
|
||||
|
|
@ -199,6 +240,47 @@ const isOverdue = computed(() => {
|
|||
return dueDate < today && props.member.current_year_dues_paid !== 'true';
|
||||
});
|
||||
|
||||
// Calculate next dues date (1 year from when they last paid)
|
||||
const nextDuesDate = computed(() => {
|
||||
// If dues are paid, calculate 1 year from payment date
|
||||
if (props.member.current_year_dues_paid === 'true' && props.member.membership_date_paid) {
|
||||
const lastPaidDate = new Date(props.member.membership_date_paid);
|
||||
const nextDue = new Date(lastPaidDate);
|
||||
nextDue.setFullYear(nextDue.getFullYear() + 1);
|
||||
return nextDue.toISOString().split('T')[0]; // Return as date string
|
||||
}
|
||||
|
||||
// If not paid but has a due date, use that
|
||||
if (props.member.payment_due_date) {
|
||||
return props.member.payment_due_date;
|
||||
}
|
||||
|
||||
// Fallback: 1 year from member since date
|
||||
if (props.member.member_since) {
|
||||
const memberSince = new Date(props.member.member_since);
|
||||
const nextDue = new Date(memberSince);
|
||||
nextDue.setFullYear(nextDue.getFullYear() + 1);
|
||||
return nextDue.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
// Check if dues are coming due within 2 months
|
||||
const isDuesComingDue = computed(() => {
|
||||
// Only show warning if dues are currently paid
|
||||
if (props.member.current_year_dues_paid !== 'true') return false;
|
||||
if (!nextDuesDate.value) return false;
|
||||
|
||||
const today = new Date();
|
||||
const dueDate = new Date(nextDuesDate.value);
|
||||
const twoMonthsFromNow = new Date();
|
||||
twoMonthsFromNow.setMonth(twoMonthsFromNow.getMonth() + 2);
|
||||
|
||||
// Show warning if due date is within the next 2 months
|
||||
return dueDate <= twoMonthsFromNow && dueDate > today;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const formatDate = (dateString: string): string => {
|
||||
if (!dateString) return '';
|
||||
|
|
@ -223,6 +305,7 @@ const formatDate = (dateString: string): string => {
|
|||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.member-card:hover {
|
||||
|
|
@ -245,6 +328,45 @@ const formatDate = (dateString: string): string => {
|
|||
z-index: 2;
|
||||
}
|
||||
|
||||
.member-action-buttons {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.member-action-buttons .v-btn {
|
||||
pointer-events: all;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.nationality-display {
|
||||
min-height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.flags-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flag-item {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.flag-item:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.country-names {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.member-info {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
|
@ -271,15 +393,6 @@ const formatDate = (dateString: string): string => {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.v-card-actions {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.v-card-actions .v-btn {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 600px) {
|
||||
.member-card {
|
||||
|
|
@ -290,6 +403,11 @@ const formatDate = (dateString: string): string => {
|
|||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.member-action-buttons {
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation for status changes */
|
||||
|
|
|
|||
|
|
@ -380,9 +380,19 @@ const filteredMembers = computed(() => {
|
|||
});
|
||||
|
||||
const totalMembers = computed(() => members.value.length);
|
||||
const activeMembers = computed(() =>
|
||||
members.value.filter(m => m.membership_status === 'Active').length
|
||||
);
|
||||
const activeMembers = computed(() => {
|
||||
// Temporary debug logging
|
||||
console.log('Members data for active count:');
|
||||
members.value.forEach((m, i) => {
|
||||
if (i < 5) { // Only log first 5 to avoid spam
|
||||
console.log(`${m.FullName}: status="${m.membership_status}", type=${typeof m.membership_status}`);
|
||||
}
|
||||
});
|
||||
|
||||
const activeCount = members.value.filter(m => m.membership_status === 'Active').length;
|
||||
console.log(`Active members count: ${activeCount} out of ${members.value.length} total`);
|
||||
return activeCount;
|
||||
});
|
||||
const paidDuesMembers = computed(() =>
|
||||
members.value.filter(m => m.current_year_dues_paid === 'true').length
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue