Add comprehensive dues tracking with overdue calculations and enhanced UI
All checks were successful
Build And Push Image / docker (push) Successful in 3m17s
All checks were successful
Build And Push Image / docker (push) Successful in 3m17s
This commit is contained in:
@@ -51,24 +51,57 @@
|
||||
|
||||
<!-- Dues Information -->
|
||||
<div class="dues-info mb-3">
|
||||
<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>
|
||||
{{ status === 'overdue' ? 'Was Due' : 'Due Date' }}
|
||||
</span>
|
||||
<span class="text-body-2 font-weight-bold" :class="status === 'overdue' ? 'text-error' : 'text-warning'">
|
||||
{{ formatDate(member.payment_due_date) }}
|
||||
</span>
|
||||
<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-if="daysDifference !== null" class="d-flex justify-space-between align-center">
|
||||
<span class="text-body-2 text-medium-emphasis">
|
||||
<v-icon size="14" class="mr-1">{{ status === 'overdue' ? 'mdi-clock-alert' : 'mdi-clock' }}</v-icon>
|
||||
{{ status === 'overdue' ? 'Days Overdue' : 'Days Until Due' }}
|
||||
</span>
|
||||
<span class="text-body-2 font-weight-bold" :class="status === 'overdue' ? 'text-error' : 'text-warning'">
|
||||
{{ Math.abs(daysDifference) }} days
|
||||
</span>
|
||||
|
||||
<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>
|
||||
|
||||
@@ -130,8 +163,16 @@
|
||||
<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: Member;
|
||||
member: DuesMember;
|
||||
status: 'overdue' | 'upcoming';
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
@@ -173,13 +173,65 @@ const snackbar = ref({
|
||||
color: 'success'
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if a member is in their grace period
|
||||
* Uses the same logic as dues-status API
|
||||
*/
|
||||
const isInGracePeriod = computed(() => {
|
||||
if (!memberData.value?.payment_due_date) return false;
|
||||
|
||||
try {
|
||||
const dueDate = new Date(memberData.value.payment_due_date);
|
||||
const today = new Date();
|
||||
return dueDate > today;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if a member's last payment is over 1 year old
|
||||
* Uses the same logic as dues-status API
|
||||
*/
|
||||
const isPaymentOverOneYear = computed(() => {
|
||||
if (!memberData.value?.membership_date_paid) return false;
|
||||
|
||||
try {
|
||||
const lastPaidDate = new Date(memberData.value.membership_date_paid);
|
||||
const oneYearFromPayment = new Date(lastPaidDate);
|
||||
oneYearFromPayment.setFullYear(oneYearFromPayment.getFullYear() + 1);
|
||||
const today = new Date();
|
||||
|
||||
return today > oneYearFromPayment;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if dues are actually current
|
||||
* Uses the same logic as dues-status API and MemberCard
|
||||
*/
|
||||
const isDuesActuallyCurrent = computed(() => {
|
||||
if (!memberData.value) return false;
|
||||
|
||||
const paymentTooOld = isPaymentOverOneYear.value;
|
||||
const duesCurrentlyPaid = memberData.value.current_year_dues_paid === 'true';
|
||||
const gracePeriod = isInGracePeriod.value;
|
||||
|
||||
// Member is NOT overdue if they're in grace period OR (dues paid AND payment not too old)
|
||||
const isOverdue = paymentTooOld || (!duesCurrentlyPaid && !gracePeriod);
|
||||
|
||||
return !isOverdue;
|
||||
});
|
||||
|
||||
// Computed properties
|
||||
const shouldShowBanner = computed(() => {
|
||||
if (!user.value || !memberData.value) return false;
|
||||
if (dismissed.value) return false;
|
||||
|
||||
// Show banner if member exists and has unpaid dues
|
||||
return memberData.value.current_year_dues_paid === 'false';
|
||||
// Show banner if dues are NOT current
|
||||
return !isDuesActuallyCurrent.value;
|
||||
});
|
||||
|
||||
const daysRemaining = computed(() => {
|
||||
|
||||
@@ -261,39 +261,54 @@ const statusColor = computed(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// Helper to check if dues are actually current (paid within last 12 months)
|
||||
const isDuesActuallyCurrent = computed(() => {
|
||||
if (props.member.current_year_dues_paid !== 'true') return false;
|
||||
/**
|
||||
* Check if a member is in their grace period
|
||||
* Uses the same logic as dues-status API
|
||||
*/
|
||||
const isInGracePeriod = computed(() => {
|
||||
if (!props.member.payment_due_date) return false;
|
||||
|
||||
if (!props.member.membership_date_paid) {
|
||||
// If marked as paid but no payment date, consider it invalid/overdue
|
||||
try {
|
||||
const dueDate = new Date(props.member.payment_due_date);
|
||||
const today = new Date();
|
||||
return dueDate > today;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
const paymentDate = new Date(props.member.membership_date_paid);
|
||||
const oneYearAgo = new Date();
|
||||
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
||||
|
||||
return paymentDate > oneYearAgo;
|
||||
});
|
||||
|
||||
// Helper to check if member is in grace period (new members get 1 month)
|
||||
const isInGracePeriod = computed(() => {
|
||||
// For existing members, check if they have member_since and it's within 1 month
|
||||
if (props.member.member_since) {
|
||||
const memberSince = new Date(props.member.member_since);
|
||||
const oneMonthLater = new Date(memberSince);
|
||||
oneMonthLater.setMonth(oneMonthLater.getMonth() + 1);
|
||||
return new Date() < oneMonthLater && props.member.current_year_dues_paid !== 'true';
|
||||
}
|
||||
/**
|
||||
* Check if a member's last payment is over 1 year old
|
||||
* Uses the same logic as dues-status API
|
||||
*/
|
||||
const isPaymentOverOneYear = computed(() => {
|
||||
if (!props.member.membership_date_paid) return false;
|
||||
|
||||
// If no member_since but has payment_due_date in the future, assume in grace period
|
||||
if (props.member.payment_due_date) {
|
||||
const dueDate = new Date(props.member.payment_due_date);
|
||||
return new Date() < dueDate && props.member.current_year_dues_paid !== 'true';
|
||||
try {
|
||||
const lastPaidDate = new Date(props.member.membership_date_paid);
|
||||
const oneYearFromPayment = new Date(lastPaidDate);
|
||||
oneYearFromPayment.setFullYear(oneYearFromPayment.getFullYear() + 1);
|
||||
const today = new Date();
|
||||
|
||||
return today > oneYearFromPayment;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if dues are actually current
|
||||
* Uses the same logic as dues-status API
|
||||
*/
|
||||
const isDuesActuallyCurrent = computed(() => {
|
||||
const paymentTooOld = isPaymentOverOneYear.value;
|
||||
const duesCurrentlyPaid = props.member.current_year_dues_paid === 'true';
|
||||
const gracePeriod = isInGracePeriod.value;
|
||||
|
||||
return false;
|
||||
// Member is NOT overdue if they're in grace period OR (dues paid AND payment not too old)
|
||||
const isOverdue = paymentTooOld || (!duesCurrentlyPaid && !gracePeriod);
|
||||
|
||||
return !isOverdue;
|
||||
});
|
||||
|
||||
const duesColor = computed(() => {
|
||||
|
||||
Reference in New Issue
Block a user