monacousa-portal/components/DuesOverdueBanner.vue

277 lines
7.0 KiB
Vue

<template>
<v-alert
v-if="overdueCount > 0 && !dismissed"
type="warning"
variant="elevated"
class="dues-overdue-banner mb-6"
prominent
border="start"
>
<template #prepend>
<v-icon size="32">mdi-alert-circle</v-icon>
</template>
<template #title>
<span class="text-h6 font-weight-bold">
Dues Overdue - {{ overdueCount }} Member{{ overdueCount > 1 ? 's' : '' }} Affected
</span>
</template>
<div class="mt-2">
<p class="mb-3">
{{ overdueCount }} member{{ overdueCount > 1 ? 's have' : ' has' }} dues that are more than 1 year overdue.
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>
<ProfileAvatar
:member-id="member.memberId"
:member-name="member.name"
size="small"
class="mr-3"
/>
</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"
variant="elevated"
size="small"
@click="$emit('view-overdue')"
>
<v-icon start>mdi-eye</v-icon>
View Overdue Members
</v-btn>
<v-btn
v-if="canUpdateStatuses"
color="primary"
variant="outlined"
size="small"
:loading="updatingStatuses"
@click="updateOverdueStatuses"
>
<v-icon start>mdi-refresh</v-icon>
Update Member Statuses
</v-btn>
<v-btn
v-if="canSendReminders"
color="secondary"
variant="outlined"
size="small"
@click="$emit('send-reminders')"
>
<v-icon start>mdi-email-multiple</v-icon>
Send Reminders
</v-btn>
<v-spacer />
<v-btn
icon
size="small"
variant="text"
@click="dismissed = true"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</div>
</div>
</v-alert>
</template>
<script setup lang="ts">
import ProfileAvatar from '~/components/ProfileAvatar.vue';
interface OverdueMember {
id: string;
name: string;
email: string;
status: string;
overdueDuration: string;
totalMonthsOverdue: number;
isInactive: boolean;
}
interface Props {
overdueCount: number;
canUpdateStatuses?: boolean;
canSendReminders?: boolean;
refreshTrigger?: number;
}
interface Emits {
(e: 'view-overdue'): void;
(e: 'send-reminders'): void;
(e: 'statuses-updated', count: number): void;
}
const props = withDefaults(defineProps<Props>(), {
canUpdateStatuses: false,
canSendReminders: false,
refreshTrigger: 0
});
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 () => {
updatingStatuses.value = true;
try {
const response = await $fetch<{
success: boolean;
data: { updatedCount: number };
message?: string;
}>('/api/members/update-overdue-statuses', {
method: 'POST'
});
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');
}
} catch (error: any) {
console.error('Error updating overdue statuses:', error);
// Show error notification if needed
} finally {
updatingStatuses.value = false;
}
};
// 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>
<style scoped>
.dues-overdue-banner {
border-radius: 12px !important;
box-shadow: 0 4px 12px rgba(255, 152, 0, 0.2) !important;
}
.dues-overdue-banner :deep(.v-alert__content) {
width: 100%;
}
.gap-2 {
gap: 8px;
}
/* Mobile responsive */
@media (max-width: 600px) {
.d-flex.flex-wrap {
flex-direction: column;
align-items: stretch !important;
}
.d-flex.flex-wrap .v-btn {
margin-bottom: 8px;
}
.v-spacer {
display: none;
}
}
</style>