updates
Build And Push Image / docker (push) Successful in 1m43s
Details
Build And Push Image / docker (push) Successful in 1m43s
Details
This commit is contained in:
parent
df6d549573
commit
e02d6c3e33
|
|
@ -224,8 +224,9 @@
|
||||||
<v-btn
|
<v-btn
|
||||||
variant="text"
|
variant="text"
|
||||||
size="small"
|
size="small"
|
||||||
:href="`mailto:${member.email}?subject=MonacoUSA Membership Dues Reminder`"
|
:loading="emailLoading"
|
||||||
target="_blank"
|
:disabled="!member.email"
|
||||||
|
@click="sendDuesReminder"
|
||||||
v-if="member.email"
|
v-if="member.email"
|
||||||
>
|
>
|
||||||
<v-icon start size="16">mdi-email</v-icon>
|
<v-icon start size="16">mdi-email</v-icon>
|
||||||
|
|
@ -280,6 +281,9 @@ const showPaymentDateDialog = ref(false);
|
||||||
const selectedPaymentDate = ref('');
|
const selectedPaymentDate = ref('');
|
||||||
const selectedPaymentModel = ref<Date | null>(null);
|
const selectedPaymentModel = ref<Date | null>(null);
|
||||||
|
|
||||||
|
// Reactive state for email sending
|
||||||
|
const emailLoading = ref(false);
|
||||||
|
|
||||||
// Initialize with today's date when dialog opens
|
// Initialize with today's date when dialog opens
|
||||||
watch(showPaymentDateDialog, (isOpen) => {
|
watch(showPaymentDateDialog, (isOpen) => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
|
|
@ -438,6 +442,38 @@ const confirmMarkAsPaid = async () => {
|
||||||
// You could show an error message here if needed
|
// You could show an error message here if needed
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendDuesReminder = async () => {
|
||||||
|
if (!props.member.email || emailLoading.value) return;
|
||||||
|
|
||||||
|
emailLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Determine the reminder type based on the member's status
|
||||||
|
const reminderType = props.status === 'overdue' ? 'overdue' : 'due-soon';
|
||||||
|
|
||||||
|
const response = await $fetch<{
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
data: any;
|
||||||
|
}>(`/api/members/${props.member.Id}/send-dues-reminder`, {
|
||||||
|
method: 'post',
|
||||||
|
body: {
|
||||||
|
reminderType
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.success) {
|
||||||
|
console.log(`Dues reminder sent successfully to ${props.member.email}`);
|
||||||
|
// You could show a success toast here if needed
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error sending dues reminder:', error);
|
||||||
|
// You could show an error toast here if needed
|
||||||
|
} finally {
|
||||||
|
emailLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,21 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div v-if="canEdit || canDelete || (!member.keycloak_id && canCreatePortalAccount)" class="member-action-buttons">
|
<div v-if="canEdit || canDelete || (!member.keycloak_id && canCreatePortalAccount) || shouldShowEmailButton" class="member-action-buttons">
|
||||||
|
<!-- Email Button for Overdue/Due Soon Members -->
|
||||||
|
<v-btn
|
||||||
|
v-if="shouldShowEmailButton"
|
||||||
|
icon
|
||||||
|
size="small"
|
||||||
|
variant="text"
|
||||||
|
:color="isOverdue ? 'error' : 'warning'"
|
||||||
|
:loading="emailLoading"
|
||||||
|
@click.stop="sendDuesReminder"
|
||||||
|
:title="'Send Dues Reminder to ' + member.FullName"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-email-alert</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="canEdit"
|
v-if="canEdit"
|
||||||
icon
|
icon
|
||||||
|
|
@ -409,6 +423,14 @@ const isDuesComingDue = computed(() => {
|
||||||
return dueDate <= twoMonthsFromNow && dueDate > today;
|
return dueDate <= twoMonthsFromNow && dueDate > today;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Email functionality
|
||||||
|
const emailLoading = ref(false);
|
||||||
|
|
||||||
|
const shouldShowEmailButton = computed(() => {
|
||||||
|
// Only show email button if member has email and is overdue or dues coming due
|
||||||
|
return !!(props.member.email && (isOverdue.value || isDuesComingDue.value));
|
||||||
|
});
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const formatDate = (dateString: string): string => {
|
const formatDate = (dateString: string): string => {
|
||||||
if (!dateString) return '';
|
if (!dateString) return '';
|
||||||
|
|
@ -424,6 +446,38 @@ const formatDate = (dateString: string): string => {
|
||||||
return dateString;
|
return dateString;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendDuesReminder = async () => {
|
||||||
|
if (!props.member.email || emailLoading.value) return;
|
||||||
|
|
||||||
|
emailLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Determine the reminder type based on the member's status
|
||||||
|
const reminderType = isOverdue.value ? 'overdue' : 'due-soon';
|
||||||
|
|
||||||
|
const response = await $fetch<{
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
data: any;
|
||||||
|
}>(`/api/members/${props.member.Id}/send-dues-reminder`, {
|
||||||
|
method: 'post',
|
||||||
|
body: {
|
||||||
|
reminderType
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.success) {
|
||||||
|
console.log(`Dues reminder sent successfully to ${props.member.email}`);
|
||||||
|
// You could show a success toast here if needed
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error sending dues reminder:', error);
|
||||||
|
// You could show an error toast here if needed
|
||||||
|
} finally {
|
||||||
|
emailLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,29 @@
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<v-card-actions class="pa-6 pt-0">
|
<v-card-actions class="pa-6 pt-0">
|
||||||
|
<!-- Email Button (Board/Admin only for overdue/due soon members) -->
|
||||||
|
<v-btn
|
||||||
|
v-if="shouldShowEmailButton"
|
||||||
|
variant="outlined"
|
||||||
|
:color="isOverdue ? 'error' : 'warning'"
|
||||||
|
:loading="emailLoading"
|
||||||
|
@click="sendDuesReminder"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-email-alert</v-icon>
|
||||||
|
Send Dues Reminder
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<!-- Mark Dues as Paid Button (Board/Admin only) -->
|
||||||
|
<v-btn
|
||||||
|
v-if="shouldShowMarkAsPaidButton"
|
||||||
|
variant="outlined"
|
||||||
|
color="success"
|
||||||
|
@click="showPaymentDateDialog = true"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-cash</v-icon>
|
||||||
|
Mark Dues as Paid
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn
|
<v-btn
|
||||||
variant="text"
|
variant="text"
|
||||||
|
|
@ -226,6 +249,80 @@
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
|
|
||||||
|
<div class="date-picker-wrapper">
|
||||||
|
<label class="date-picker-label">Payment Date</label>
|
||||||
|
<VueDatePicker
|
||||||
|
v-model="selectedPaymentModel"
|
||||||
|
:timezone="{
|
||||||
|
timezone: 'Europe/Monaco',
|
||||||
|
emitTimezone: 'UTC'
|
||||||
|
}"
|
||||||
|
:format="'dd/MM/yyyy (Monaco)'"
|
||||||
|
:max-date="new Date()"
|
||||||
|
placeholder="Select payment date"
|
||||||
|
:enable-time-picker="false"
|
||||||
|
auto-apply
|
||||||
|
:clearable="false"
|
||||||
|
:required="true"
|
||||||
|
@update:model-value="handleDateUpdate"
|
||||||
|
/>
|
||||||
|
<div class="text-caption text-medium-emphasis mt-1">
|
||||||
|
Select the date when the payment was received
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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"
|
||||||
|
@click="markDuesAsPaid"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-check-circle</v-icon>
|
||||||
|
Confirm Payment
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -252,6 +349,17 @@ defineEmits<Emits>();
|
||||||
const showImageLightbox = ref(false);
|
const showImageLightbox = ref(false);
|
||||||
const lightboxImageUrl = ref<string | null>(null);
|
const lightboxImageUrl = ref<string | null>(null);
|
||||||
|
|
||||||
|
// Email functionality
|
||||||
|
const emailLoading = ref(false);
|
||||||
|
|
||||||
|
// Payment dialog state
|
||||||
|
const showPaymentDateDialog = ref(false);
|
||||||
|
const selectedPaymentDate = ref('');
|
||||||
|
const selectedPaymentModel = ref<Date | null>(null);
|
||||||
|
|
||||||
|
// Auth composable
|
||||||
|
const { user, isAdmin, isBoard } = useAuth();
|
||||||
|
|
||||||
// Computed properties
|
// Computed properties
|
||||||
const memberInitials = computed(() => {
|
const memberInitials = computed(() => {
|
||||||
if (!props.member) return '';
|
if (!props.member) return '';
|
||||||
|
|
@ -318,7 +426,80 @@ const isOverdue = computed(() => {
|
||||||
return dueDate < today && props.member.current_year_dues_paid !== 'true';
|
return dueDate < today && props.member.current_year_dues_paid !== 'true';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if dues are coming due within 2 months
|
||||||
|
const isDuesComingDue = computed(() => {
|
||||||
|
if (!props.member || props.member.current_year_dues_paid !== 'true') return false;
|
||||||
|
|
||||||
|
// Calculate next due date (1 year from last payment)
|
||||||
|
if (props.member.membership_date_paid) {
|
||||||
|
const lastPaidDate = new Date(props.member.membership_date_paid);
|
||||||
|
const nextDue = new Date(lastPaidDate);
|
||||||
|
nextDue.setFullYear(nextDue.getFullYear() + 1);
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const twoMonthsFromNow = new Date();
|
||||||
|
twoMonthsFromNow.setMonth(twoMonthsFromNow.getMonth() + 2);
|
||||||
|
|
||||||
|
return nextDue <= twoMonthsFromNow && nextDue > today;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const shouldShowEmailButton = computed(() => {
|
||||||
|
// Only show email button for board/admin users for members with email and dues issues
|
||||||
|
const hasPermission = isAdmin.value || isBoard.value;
|
||||||
|
const hasEmail = !!props.member?.email;
|
||||||
|
const hasDuesIssue = isOverdue.value || isDuesComingDue.value;
|
||||||
|
|
||||||
|
return hasPermission && hasEmail && hasDuesIssue;
|
||||||
|
});
|
||||||
|
|
||||||
|
const shouldShowMarkAsPaidButton = computed(() => {
|
||||||
|
// Only show mark as paid button for board/admin users for members with outstanding dues
|
||||||
|
const hasPermission = isAdmin.value || isBoard.value;
|
||||||
|
const hasOutstandingDues = props.member?.current_year_dues_paid !== 'true';
|
||||||
|
|
||||||
|
return hasPermission && hasOutstandingDues;
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize with today's date when dialog opens
|
||||||
|
watch(showPaymentDateDialog, (isOpen) => {
|
||||||
|
if (isOpen) {
|
||||||
|
const today = new Date();
|
||||||
|
selectedPaymentModel.value = today;
|
||||||
|
selectedPaymentDate.value = todayDate.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
|
const handleDateUpdate = (date: Date | null) => {
|
||||||
|
if (date) {
|
||||||
|
selectedPaymentDate.value = date.toISOString().split('T')[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelPaymentDialog = () => {
|
||||||
|
showPaymentDateDialog.value = false;
|
||||||
|
selectedPaymentDate.value = '';
|
||||||
|
selectedPaymentModel.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
const formatDate = (dateString: string): string => {
|
const formatDate = (dateString: string): string => {
|
||||||
if (!dateString) return '';
|
if (!dateString) return '';
|
||||||
|
|
||||||
|
|
@ -334,6 +515,67 @@ const formatDate = (dateString: string): string => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendDuesReminder = async () => {
|
||||||
|
if (!props.member?.email || emailLoading.value) return;
|
||||||
|
|
||||||
|
emailLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Determine the reminder type based on the member's status
|
||||||
|
const reminderType = isOverdue.value ? 'overdue' : 'due-soon';
|
||||||
|
|
||||||
|
const response = await $fetch<{
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
data: any;
|
||||||
|
}>(`/api/members/${props.member.Id}/send-dues-reminder`, {
|
||||||
|
method: 'post',
|
||||||
|
body: {
|
||||||
|
reminderType
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response?.success) {
|
||||||
|
console.log(`Dues reminder sent successfully to ${props.member.email}`);
|
||||||
|
// You could show a success toast here if needed
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error sending dues reminder:', error);
|
||||||
|
// You could show an error toast here if needed
|
||||||
|
} finally {
|
||||||
|
emailLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const markDuesAsPaid = async () => {
|
||||||
|
if (!props.member || !selectedPaymentDate.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
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) {
|
||||||
|
// Update the member data
|
||||||
|
Object.assign(props.member, 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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const openImageLightbox = async () => {
|
const openImageLightbox = async () => {
|
||||||
if (!props.member?.member_id) return;
|
if (!props.member?.member_id) return;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue