feat: Enhanced dues overdue system with detailed time tracking
All checks were successful
Build And Push Image / docker (push) Successful in 2m42s
All checks were successful
Build And Push Image / docker (push) Successful in 2m42s
- Enhanced update-overdue-statuses API to calculate and return specific overdue durations (years/months) - Updated overdue-count API to provide detailed member information with overdue durations - Enhanced DuesOverdueBanner component to display expandable list of overdue members with their specific overdue time - Added automatic marking of members as inactive when dues are over 1 year overdue - Improved UI to show 'Dues Overdue - X Members Affected' with detailed breakdown - Members with overdue dues now display exact time overdue (e.g., '2 years 3 months overdue') - Added proper TypeScript interfaces for overdue member data - Enhanced banner shows inactive status and overdue duration for each affected member
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-alert
|
||||
v-if="overdueCount > 0"
|
||||
v-if="overdueCount > 0 && !dismissed"
|
||||
type="warning"
|
||||
variant="elevated"
|
||||
class="dues-overdue-banner mb-6"
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<template #title>
|
||||
<span class="text-h6 font-weight-bold">
|
||||
{{ overdueCount }} Member{{ overdueCount > 1 ? 's' : '' }} with Overdue Dues
|
||||
Dues Overdue - {{ overdueCount }} Member{{ overdueCount > 1 ? 's' : '' }} Affected
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -23,6 +23,70 @@
|
||||
These accounts have been automatically marked as inactive.
|
||||
</p>
|
||||
|
||||
<!-- Detailed Overdue List -->
|
||||
<v-expansion-panels
|
||||
v-if="overdueMembers && overdueMembers.length > 0"
|
||||
class="mb-4"
|
||||
variant="accordion"
|
||||
>
|
||||
<v-expansion-panel
|
||||
title="View Overdue Details"
|
||||
:text="`Click to see all ${overdueCount} overdue members and their specific overdue durations`"
|
||||
>
|
||||
<template #text>
|
||||
<v-list class="pa-0">
|
||||
<v-list-item
|
||||
v-for="member in overdueMembers"
|
||||
:key="member.id"
|
||||
class="overdue-member-item"
|
||||
>
|
||||
<template #prepend>
|
||||
<v-avatar
|
||||
:color="member.isInactive ? 'grey' : 'warning'"
|
||||
size="32"
|
||||
class="mr-3"
|
||||
>
|
||||
<v-icon color="white" size="16">
|
||||
{{ member.isInactive ? 'mdi-account-off' : 'mdi-account-alert' }}
|
||||
</v-icon>
|
||||
</v-avatar>
|
||||
</template>
|
||||
|
||||
<v-list-item-title class="font-weight-medium">
|
||||
{{ member.name }}
|
||||
</v-list-item-title>
|
||||
|
||||
<v-list-item-subtitle>
|
||||
{{ member.email }}
|
||||
</v-list-item-subtitle>
|
||||
|
||||
<template #append>
|
||||
<div class="text-right">
|
||||
<v-chip
|
||||
:color="member.isInactive ? 'grey' : 'error'"
|
||||
size="small"
|
||||
variant="flat"
|
||||
class="mb-1"
|
||||
>
|
||||
<v-icon start size="12">mdi-clock-alert</v-icon>
|
||||
{{ member.overdueDuration }}
|
||||
</v-chip>
|
||||
<br>
|
||||
<v-chip
|
||||
:color="member.isInactive ? 'grey' : 'warning'"
|
||||
size="x-small"
|
||||
variant="tonal"
|
||||
>
|
||||
{{ member.isInactive ? 'Inactive' : member.status }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 align-center">
|
||||
<v-btn
|
||||
color="warning"
|
||||
@@ -73,6 +137,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface OverdueMember {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
status: string;
|
||||
overdueDuration: string;
|
||||
totalMonthsOverdue: number;
|
||||
isInactive: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
overdueCount: number;
|
||||
canUpdateStatuses?: boolean;
|
||||
@@ -97,6 +171,27 @@ const emit = defineEmits<Emits>();
|
||||
// State
|
||||
const dismissed = ref(false);
|
||||
const updatingStatuses = ref(false);
|
||||
const overdueMembers = ref<OverdueMember[]>([]);
|
||||
|
||||
// Load overdue member details
|
||||
const loadOverdueDetails = async () => {
|
||||
try {
|
||||
const response = await $fetch<{
|
||||
success: boolean;
|
||||
data: {
|
||||
count: number;
|
||||
overdueMembers: OverdueMember[];
|
||||
};
|
||||
}>('/api/members/overdue-count');
|
||||
|
||||
if (response.success) {
|
||||
overdueMembers.value = response.data.overdueMembers || [];
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error loading overdue details:', error);
|
||||
overdueMembers.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// Update overdue member statuses
|
||||
const updateOverdueStatuses = async () => {
|
||||
@@ -114,6 +209,9 @@ const updateOverdueStatuses = async () => {
|
||||
if (response.success) {
|
||||
emit('statuses-updated', response.data.updatedCount);
|
||||
console.log(`Updated ${response.data.updatedCount} overdue member statuses`);
|
||||
|
||||
// Refresh overdue details after update
|
||||
await loadOverdueDetails();
|
||||
} else {
|
||||
throw new Error(response.message || 'Failed to update statuses');
|
||||
}
|
||||
@@ -128,12 +226,21 @@ const updateOverdueStatuses = async () => {
|
||||
// Reset dismissed state when refresh trigger changes
|
||||
watch(() => props.refreshTrigger, () => {
|
||||
dismissed.value = false;
|
||||
loadOverdueDetails(); // Refresh data
|
||||
});
|
||||
|
||||
// Watch for overdueCount changes and reset dismissed
|
||||
watch(() => props.overdueCount, (newCount, oldCount) => {
|
||||
if (newCount > oldCount) {
|
||||
dismissed.value = false;
|
||||
loadOverdueDetails(); // Load details when count changes
|
||||
}
|
||||
});
|
||||
|
||||
// Load details on component mount
|
||||
onMounted(() => {
|
||||
if (props.overdueCount > 0) {
|
||||
loadOverdueDetails();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user