Add payment date selection for dues payments and improve member viewing
All checks were successful
Build And Push Image / docker (push) Successful in 3m23s

- Add payment date dialog when marking dues as paid with date validation
- Replace direct member view emission with dedicated ViewMemberDialog component
- Add backend support for custom payment dates in mark-dues-paid endpoint
- Prevent future date selection for payment records
- Improve user workflow for viewing and editing member details
This commit is contained in:
2025-08-11 15:41:33 +02:00
parent abf6ade8cd
commit f1a462094a
5 changed files with 194 additions and 10 deletions

View File

@@ -42,7 +42,7 @@
:member="member"
status="overdue"
@mark-paid="handleMarkPaid"
@view-member="$emit('view-member', member)"
@view-member="handleViewMember"
:loading="loading[member.Id]"
/>
</v-col>
@@ -69,7 +69,7 @@
:member="member"
status="upcoming"
@mark-paid="handleMarkPaid"
@view-member="$emit('view-member', member)"
@view-member="handleViewMember"
:loading="loading[member.Id]"
/>
</v-col>
@@ -99,6 +99,13 @@
View All Members
</v-btn>
</v-card-actions>
<!-- View Member Dialog -->
<ViewMemberDialog
v-model="showViewDialog"
:member="selectedMember"
@edit="handleEditMember"
/>
</v-card>
</template>
@@ -125,6 +132,10 @@ const upcomingMembers = ref<Member[]>([]);
const loading = ref<Record<string, boolean>>({});
const refreshLoading = ref(false);
// View member dialog state
const showViewDialog = ref(false);
const selectedMember = ref<Member | null>(null);
// Load dues data
const loadDuesData = async () => {
refreshLoading.value = true;
@@ -184,6 +195,20 @@ const handleMarkPaid = async (member: Member) => {
}
};
// Handle view member
const handleViewMember = (member: Member) => {
selectedMember.value = member;
showViewDialog.value = true;
};
// Handle edit member (from the view dialog)
const handleEditMember = (member: Member) => {
// Close the view dialog first
showViewDialog.value = false;
// Emit the view-member event which should trigger the edit dialog in the parent component
emit('view-member', member);
};
// Refresh data
const refreshData = () => {
loadDuesData();

View File

@@ -125,7 +125,7 @@
variant="elevated"
size="small"
:loading="loading"
@click="$emit('mark-paid', member)"
@click="showPaymentDateDialog = true"
block
>
<v-icon start size="16">mdi-check-circle</v-icon>
@@ -133,6 +133,71 @@
</v-btn>
</v-card-actions>
<!-- Payment Date Selection Dialog -->
<v-dialog v-model="showPaymentDateDialog" max-width="400">
<v-card>
<v-card-title class="text-h6 pa-4">
<v-icon left color="success">mdi-calendar-check</v-icon>
Mark Dues as Paid
</v-card-title>
<v-card-text class="pa-4">
<div class="mb-4">
<h4 class="text-subtitle-1 mb-2">
{{ member.FullName || `${member.first_name} ${member.last_name}` }}
</h4>
<p class="text-body-2 text-medium-emphasis">
Select the date when the dues payment was received:
</p>
</div>
<v-text-field
v-model="selectedPaymentDate"
label="Payment Date"
type="date"
variant="outlined"
:max="todayDate"
prepend-inner-icon="mdi-calendar"
hint="Select the date when the payment was received"
persistent-hint
class="mb-2"
/>
<v-alert
v-if="selectedPaymentDate && isDateInFuture"
type="warning"
variant="tonal"
class="mt-2"
density="compact"
>
<v-icon start>mdi-information</v-icon>
Future dates are not allowed. Please select today or an earlier date.
</v-alert>
</v-card-text>
<v-card-actions class="pa-4 pt-0">
<v-spacer />
<v-btn
color="grey"
variant="text"
@click="cancelPaymentDialog"
>
Cancel
</v-btn>
<v-btn
color="success"
variant="elevated"
:disabled="!selectedPaymentDate || isDateInFuture"
:loading="loading"
@click="confirmMarkAsPaid"
>
<v-icon start>mdi-check-circle</v-icon>
Confirm Payment
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Quick Actions -->
<v-card-actions class="pa-4 pt-0">
<v-btn
@@ -186,7 +251,18 @@ const props = withDefaults(defineProps<Props>(), {
loading: false
});
defineEmits<Emits>();
const emit = defineEmits<Emits>();
// Reactive state for payment date dialog
const showPaymentDateDialog = ref(false);
const selectedPaymentDate = ref('');
// Initialize with today's date when dialog opens
watch(showPaymentDateDialog, (isOpen) => {
if (isOpen) {
selectedPaymentDate.value = todayDate.value;
}
});
// Computed properties
const memberInitials = computed(() => {
@@ -195,6 +271,21 @@ const memberInitials = computed(() => {
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
});
const todayDate = computed(() => {
return new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
});
const isDateInFuture = computed(() => {
if (!selectedPaymentDate.value) return false;
const selectedDate = new Date(selectedPaymentDate.value);
const today = new Date();
today.setHours(0, 0, 0, 0); // Reset time to start of day
selectedDate.setHours(0, 0, 0, 0); // Reset time to start of day
return selectedDate > today;
});
const avatarColor = computed(() => {
const colors = ['red', 'blue', 'green', 'orange', 'purple', 'teal', 'indigo', 'pink'];
const idNumber = parseInt(props.member.Id) || 0;
@@ -239,6 +330,41 @@ const formatDate = (dateString: string): string => {
return dateString;
}
};
const cancelPaymentDialog = () => {
showPaymentDateDialog.value = false;
selectedPaymentDate.value = '';
};
const confirmMarkAsPaid = async () => {
if (!selectedPaymentDate.value || isDateInFuture.value) return;
try {
// Call the API with the selected payment date
const response = await $fetch<{
success: boolean;
data: Member;
message?: string;
}>(`/api/members/${props.member.Id}/mark-dues-paid`, {
method: 'post',
body: {
paymentDate: selectedPaymentDate.value
}
});
if (response?.success && response.data) {
// Emit the mark-paid event with the updated member data
emit('mark-paid', response.data);
// Close the dialog and reset
showPaymentDateDialog.value = false;
selectedPaymentDate.value = '';
}
} catch (error: any) {
console.error('Error marking dues as paid:', error);
// You could show an error message here if needed
}
};
</script>
<style scoped>