2025-08-12 04:25:35 +02:00
|
|
|
<template>
|
|
|
|
|
<v-dialog v-model="show" max-width="600" persistent>
|
|
|
|
|
<v-card v-if="event">
|
2025-08-13 12:27:21 +02:00
|
|
|
<v-card-title class="d-flex justify-space-between align-center">
|
2025-08-12 04:25:35 +02:00
|
|
|
<div class="d-flex align-center">
|
|
|
|
|
<v-icon class="me-2" :color="eventTypeColor">{{ eventTypeIcon }}</v-icon>
|
2025-08-13 12:27:21 +02:00
|
|
|
<span>{{ event?.title || 'Event Details' }}</span>
|
2025-08-12 04:25:35 +02:00
|
|
|
</div>
|
|
|
|
|
<v-btn
|
|
|
|
|
@click="close"
|
|
|
|
|
icon
|
|
|
|
|
variant="text"
|
|
|
|
|
size="small"
|
|
|
|
|
>
|
|
|
|
|
<v-icon>mdi-close</v-icon>
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-card-title>
|
|
|
|
|
|
|
|
|
|
<v-card-text>
|
|
|
|
|
<!-- Event Type Badge -->
|
|
|
|
|
<v-chip
|
|
|
|
|
:color="eventTypeColor"
|
|
|
|
|
size="small"
|
|
|
|
|
variant="tonal"
|
|
|
|
|
class="mb-4"
|
|
|
|
|
>
|
|
|
|
|
<v-icon start>{{ eventTypeIcon }}</v-icon>
|
|
|
|
|
{{ eventTypeLabel }}
|
|
|
|
|
</v-chip>
|
|
|
|
|
|
|
|
|
|
<!-- Event Details -->
|
|
|
|
|
<v-row class="mb-4">
|
|
|
|
|
<!-- Date & Time -->
|
|
|
|
|
<v-col cols="12">
|
|
|
|
|
<div class="d-flex align-center mb-2">
|
|
|
|
|
<v-icon class="me-2">mdi-calendar-clock</v-icon>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="font-weight-medium">{{ formatEventDate }}</div>
|
|
|
|
|
<div class="text-body-2 text-medium-emphasis">{{ formatEventTime }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</v-col>
|
|
|
|
|
|
|
|
|
|
<!-- Location -->
|
|
|
|
|
<v-col v-if="event.location" cols="12">
|
|
|
|
|
<div class="d-flex align-center mb-2">
|
|
|
|
|
<v-icon class="me-2">mdi-map-marker</v-icon>
|
|
|
|
|
<span>{{ event.location }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</v-col>
|
|
|
|
|
|
|
|
|
|
<!-- Description -->
|
|
|
|
|
<v-col v-if="event.description" cols="12">
|
|
|
|
|
<div class="d-flex align-start mb-2">
|
|
|
|
|
<v-icon class="me-2 mt-1">mdi-text</v-icon>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="font-weight-medium mb-1">Description</div>
|
2025-08-13 13:02:12 +02:00
|
|
|
<!-- Display HTML content safely -->
|
|
|
|
|
<div
|
|
|
|
|
class="text-body-2 rich-text-content"
|
|
|
|
|
v-html="event.description"
|
|
|
|
|
/>
|
2025-08-12 04:25:35 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</v-col>
|
|
|
|
|
|
|
|
|
|
<!-- Capacity -->
|
|
|
|
|
<v-col v-if="event.max_attendees" cols="12">
|
|
|
|
|
<div class="d-flex align-center mb-2">
|
|
|
|
|
<v-icon class="me-2">mdi-account-group</v-icon>
|
|
|
|
|
<div>
|
|
|
|
|
<span class="font-weight-medium">Capacity:</span>
|
|
|
|
|
<span class="ms-2">
|
|
|
|
|
{{ event.current_attendees || 0 }} / {{ event.max_attendees }}
|
|
|
|
|
</span>
|
|
|
|
|
<v-progress-linear
|
|
|
|
|
:model-value="capacityPercentage"
|
|
|
|
|
:color="capacityColor"
|
|
|
|
|
height="4"
|
|
|
|
|
class="mt-1"
|
|
|
|
|
rounded
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
|
|
|
|
|
|
|
|
|
<!-- Payment Information -->
|
|
|
|
|
<v-alert
|
|
|
|
|
v-if="event.is_paid === 'true'"
|
|
|
|
|
type="info"
|
|
|
|
|
variant="tonal"
|
|
|
|
|
class="mb-4"
|
|
|
|
|
>
|
|
|
|
|
<v-alert-title>
|
|
|
|
|
<v-icon start>mdi-currency-eur</v-icon>
|
|
|
|
|
Payment Required
|
|
|
|
|
</v-alert-title>
|
|
|
|
|
<div class="mt-2">
|
|
|
|
|
<div v-if="memberPrice && nonMemberPrice">
|
|
|
|
|
<strong>Members:</strong> €{{ memberPrice }}<br>
|
|
|
|
|
<strong>Non-Members:</strong> €{{ nonMemberPrice }}
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else-if="memberPrice">
|
|
|
|
|
<strong>Cost:</strong> €{{ memberPrice }}
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="event.member_pricing_enabled === 'false'" class="text-caption mt-1">
|
|
|
|
|
<v-icon size="small">mdi-information</v-icon>
|
|
|
|
|
Member pricing is not available for this event
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</v-alert>
|
|
|
|
|
|
|
|
|
|
<!-- RSVP Status -->
|
|
|
|
|
<v-card
|
|
|
|
|
v-if="hasRSVP"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
class="mb-4"
|
|
|
|
|
:color="rsvpStatusColor"
|
|
|
|
|
>
|
|
|
|
|
<v-card-text class="py-3">
|
|
|
|
|
<div class="d-flex align-center justify-space-between">
|
|
|
|
|
<div class="d-flex align-center">
|
|
|
|
|
<v-icon :color="rsvpStatusColor" class="me-2">{{ rsvpStatusIcon }}</v-icon>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="font-weight-medium">{{ rsvpStatusText }}</div>
|
|
|
|
|
<div v-if="userRSVP?.rsvp_notes" class="text-caption">{{ userRSVP.rsvp_notes }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<v-btn
|
|
|
|
|
@click="changeRSVP"
|
|
|
|
|
size="small"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
:color="rsvpStatusColor"
|
|
|
|
|
>
|
|
|
|
|
Change
|
|
|
|
|
</v-btn>
|
|
|
|
|
</div>
|
|
|
|
|
</v-card-text>
|
|
|
|
|
</v-card>
|
|
|
|
|
|
|
|
|
|
<!-- Payment Details (if RSVP'd to paid event) -->
|
|
|
|
|
<v-card
|
|
|
|
|
v-if="showPaymentDetails"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
class="mb-4"
|
|
|
|
|
>
|
|
|
|
|
<v-card-title class="py-3">
|
|
|
|
|
<v-icon class="me-2">mdi-bank-transfer</v-icon>
|
|
|
|
|
Payment Details
|
|
|
|
|
</v-card-title>
|
|
|
|
|
<v-card-text class="pt-0">
|
|
|
|
|
<v-row dense>
|
|
|
|
|
<v-col cols="12">
|
|
|
|
|
<div class="text-body-2">
|
|
|
|
|
<strong>Amount:</strong> €{{ paymentAmount }}
|
|
|
|
|
</div>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="12">
|
|
|
|
|
<div class="text-body-2">
|
|
|
|
|
<strong>IBAN:</strong> {{ paymentInfo.iban }}
|
|
|
|
|
</div>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="12">
|
|
|
|
|
<div class="text-body-2">
|
|
|
|
|
<strong>Recipient:</strong> {{ paymentInfo.recipient }}
|
|
|
|
|
</div>
|
|
|
|
|
</v-col>
|
|
|
|
|
<v-col cols="12">
|
|
|
|
|
<div class="text-body-2">
|
|
|
|
|
<strong>Reference:</strong> {{ userRSVP?.payment_reference }}
|
|
|
|
|
</div>
|
|
|
|
|
</v-col>
|
|
|
|
|
</v-row>
|
|
|
|
|
<v-btn
|
|
|
|
|
@click="copyPaymentDetails"
|
|
|
|
|
size="small"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
class="mt-3"
|
|
|
|
|
prepend-icon="mdi-content-copy"
|
|
|
|
|
>
|
|
|
|
|
Copy Details
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-card-text>
|
|
|
|
|
</v-card>
|
|
|
|
|
|
|
|
|
|
<!-- RSVP Form -->
|
|
|
|
|
<v-card v-if="!hasRSVP && canRSVP" variant="outlined">
|
|
|
|
|
<v-card-title class="py-3">
|
|
|
|
|
<v-icon class="me-2">mdi-account-check</v-icon>
|
|
|
|
|
RSVP to this Event
|
|
|
|
|
</v-card-title>
|
|
|
|
|
<v-card-text class="pt-0">
|
2025-08-13 12:27:21 +02:00
|
|
|
<v-form v-model="rsvpValid">
|
2025-08-13 15:14:43 +02:00
|
|
|
<!-- Guest Selection (if event allows guests) -->
|
|
|
|
|
<div v-if="allowsGuests" class="mb-4">
|
|
|
|
|
<v-card variant="tonal" class="mb-3">
|
|
|
|
|
<v-card-text class="py-3">
|
|
|
|
|
<div class="d-flex align-center mb-2">
|
|
|
|
|
<v-icon class="me-2">mdi-account-group</v-icon>
|
|
|
|
|
<span class="font-weight-medium">Bring Guests</span>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="text-body-2 text-medium-emphasis mb-3">
|
|
|
|
|
This event allows up to {{ maxGuestsAllowed }} additional guests per person.
|
|
|
|
|
</p>
|
|
|
|
|
<v-select
|
|
|
|
|
v-model="selectedGuests"
|
|
|
|
|
:items="guestOptions"
|
|
|
|
|
label="Number of Additional Guests"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
density="compact"
|
|
|
|
|
/>
|
|
|
|
|
</v-card-text>
|
|
|
|
|
</v-card>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-08-12 04:25:35 +02:00
|
|
|
<v-textarea
|
|
|
|
|
v-model="rsvpNotes"
|
|
|
|
|
label="Notes (optional)"
|
|
|
|
|
rows="2"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
class="mb-3"
|
|
|
|
|
/>
|
|
|
|
|
|
2025-08-13 15:14:43 +02:00
|
|
|
<v-btn
|
|
|
|
|
@click="submitRSVP('confirmed')"
|
|
|
|
|
color="primary"
|
|
|
|
|
:loading="rsvpLoading"
|
|
|
|
|
:disabled="isEventFull && !isWaitlistAvailable"
|
|
|
|
|
size="large"
|
|
|
|
|
block
|
|
|
|
|
class="mb-2"
|
|
|
|
|
>
|
|
|
|
|
<v-icon start>mdi-check</v-icon>
|
|
|
|
|
{{ isEventFull ? 'Join Waitlist' : 'RSVP' }}
|
|
|
|
|
</v-btn>
|
2025-08-12 04:25:35 +02:00
|
|
|
</v-form>
|
|
|
|
|
</v-card-text>
|
|
|
|
|
</v-card>
|
|
|
|
|
|
|
|
|
|
<!-- Event Full Message -->
|
|
|
|
|
<v-alert
|
|
|
|
|
v-if="isEventFull && !hasRSVP && !isWaitlistAvailable"
|
|
|
|
|
type="warning"
|
|
|
|
|
variant="tonal"
|
|
|
|
|
>
|
|
|
|
|
<v-alert-title>Event Full</v-alert-title>
|
|
|
|
|
This event has reached maximum capacity and waitlist is not available.
|
|
|
|
|
</v-alert>
|
|
|
|
|
|
|
|
|
|
<!-- Past Event Message -->
|
|
|
|
|
<v-alert
|
|
|
|
|
v-if="isPastEvent"
|
|
|
|
|
type="info"
|
|
|
|
|
variant="tonal"
|
|
|
|
|
>
|
|
|
|
|
<v-alert-title>Past Event</v-alert-title>
|
|
|
|
|
This event has already occurred.
|
|
|
|
|
</v-alert>
|
|
|
|
|
</v-card-text>
|
|
|
|
|
|
|
|
|
|
<v-card-actions class="pa-4">
|
|
|
|
|
<v-spacer />
|
|
|
|
|
<v-btn
|
|
|
|
|
@click="close"
|
|
|
|
|
variant="outlined"
|
|
|
|
|
>
|
|
|
|
|
Close
|
|
|
|
|
</v-btn>
|
|
|
|
|
</v-card-actions>
|
|
|
|
|
</v-card>
|
|
|
|
|
</v-dialog>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import type { Event, EventRSVP } from '~/utils/types';
|
|
|
|
|
import { useEvents } from '~/composables/useEvents';
|
|
|
|
|
import { format } from 'date-fns';
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
modelValue: boolean;
|
|
|
|
|
event: Event | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
'update:modelValue': [value: boolean];
|
|
|
|
|
'rsvp-updated': [event: Event];
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
const { rsvpToEvent } = useEvents();
|
|
|
|
|
|
|
|
|
|
// Reactive state
|
|
|
|
|
const rsvpValid = ref(false);
|
|
|
|
|
const rsvpLoading = ref(false);
|
|
|
|
|
const rsvpNotes = ref('');
|
2025-08-13 15:14:43 +02:00
|
|
|
const selectedGuests = ref(0);
|
2025-08-12 04:25:35 +02:00
|
|
|
|
|
|
|
|
// Computed properties
|
|
|
|
|
const show = computed({
|
|
|
|
|
get: () => props.modelValue,
|
|
|
|
|
set: (value) => emit('update:modelValue', value)
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-13 13:02:12 +02:00
|
|
|
|
2025-08-12 04:25:35 +02:00
|
|
|
const userRSVP = computed((): EventRSVP | null => {
|
|
|
|
|
return props.event?.user_rsvp || null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const hasRSVP = computed(() => !!userRSVP.value);
|
|
|
|
|
|
|
|
|
|
const canRSVP = computed(() => {
|
|
|
|
|
return props.event && !isPastEvent.value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isPastEvent = computed(() => {
|
|
|
|
|
if (!props.event) return false;
|
|
|
|
|
return new Date(props.event.start_datetime) < new Date();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isEventFull = computed(() => {
|
|
|
|
|
if (!props.event?.max_attendees) return false;
|
|
|
|
|
const maxAttendees = parseInt(props.event.max_attendees);
|
2025-08-13 12:27:21 +02:00
|
|
|
const currentAttendees = typeof props.event.current_attendees === 'string'
|
|
|
|
|
? parseInt(props.event.current_attendees) || 0
|
|
|
|
|
: props.event.current_attendees || 0;
|
2025-08-12 04:25:35 +02:00
|
|
|
return currentAttendees >= maxAttendees;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isWaitlistAvailable = computed(() => true); // Always allow waitlist for now
|
|
|
|
|
|
|
|
|
|
const eventTypeColor = computed(() => {
|
|
|
|
|
const colors = {
|
|
|
|
|
'meeting': 'blue',
|
|
|
|
|
'social': 'green',
|
|
|
|
|
'fundraiser': 'orange',
|
|
|
|
|
'workshop': 'purple',
|
|
|
|
|
'board-only': 'red'
|
|
|
|
|
};
|
|
|
|
|
return colors[props.event?.event_type as keyof typeof colors] || 'grey';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const eventTypeIcon = computed(() => {
|
|
|
|
|
const icons = {
|
|
|
|
|
'meeting': 'mdi-account-group',
|
|
|
|
|
'social': 'mdi-party-popper',
|
|
|
|
|
'fundraiser': 'mdi-heart',
|
|
|
|
|
'workshop': 'mdi-school',
|
|
|
|
|
'board-only': 'mdi-shield-account'
|
|
|
|
|
};
|
|
|
|
|
return icons[props.event?.event_type as keyof typeof icons] || 'mdi-calendar';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const eventTypeLabel = computed(() => {
|
|
|
|
|
const labels = {
|
|
|
|
|
'meeting': 'Meeting',
|
|
|
|
|
'social': 'Social Event',
|
|
|
|
|
'fundraiser': 'Fundraiser',
|
|
|
|
|
'workshop': 'Workshop',
|
|
|
|
|
'board-only': 'Board Only'
|
|
|
|
|
};
|
|
|
|
|
return labels[props.event?.event_type as keyof typeof labels] || 'Event';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const formatEventDate = computed(() => {
|
|
|
|
|
if (!props.event) return '';
|
|
|
|
|
const startDate = new Date(props.event.start_datetime);
|
|
|
|
|
const endDate = new Date(props.event.end_datetime);
|
|
|
|
|
|
|
|
|
|
if (startDate.toDateString() === endDate.toDateString()) {
|
|
|
|
|
return format(startDate, 'EEEE, MMMM d, yyyy');
|
|
|
|
|
} else {
|
|
|
|
|
return `${format(startDate, 'MMM d')} - ${format(endDate, 'MMM d, yyyy')}`;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const formatEventTime = computed(() => {
|
|
|
|
|
if (!props.event) return '';
|
|
|
|
|
const startDate = new Date(props.event.start_datetime);
|
|
|
|
|
const endDate = new Date(props.event.end_datetime);
|
|
|
|
|
|
|
|
|
|
return `${format(startDate, 'HH:mm')} - ${format(endDate, 'HH:mm')}`;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const capacityPercentage = computed(() => {
|
|
|
|
|
if (!props.event?.max_attendees) return 0;
|
|
|
|
|
const max = parseInt(props.event.max_attendees);
|
2025-08-13 12:27:21 +02:00
|
|
|
const current = typeof props.event.current_attendees === 'string'
|
|
|
|
|
? parseInt(props.event.current_attendees) || 0
|
|
|
|
|
: props.event.current_attendees || 0;
|
2025-08-12 04:25:35 +02:00
|
|
|
return (current / max) * 100;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const capacityColor = computed(() => {
|
|
|
|
|
const percentage = capacityPercentage.value;
|
|
|
|
|
if (percentage >= 100) return 'error';
|
|
|
|
|
if (percentage >= 80) return 'warning';
|
|
|
|
|
return 'success';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const memberPrice = computed(() => props.event?.cost_members);
|
|
|
|
|
const nonMemberPrice = computed(() => props.event?.cost_non_members);
|
|
|
|
|
|
|
|
|
|
const rsvpStatusColor = computed(() => {
|
|
|
|
|
const status = userRSVP.value?.rsvp_status;
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'confirmed': return 'success';
|
|
|
|
|
case 'waitlist': return 'warning';
|
|
|
|
|
case 'declined': return 'error';
|
|
|
|
|
default: return 'info';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const rsvpStatusIcon = computed(() => {
|
|
|
|
|
const status = userRSVP.value?.rsvp_status;
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'confirmed': return 'mdi-check-circle';
|
|
|
|
|
case 'waitlist': return 'mdi-clock';
|
|
|
|
|
case 'declined': return 'mdi-close-circle';
|
|
|
|
|
default: return 'mdi-help-circle';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const rsvpStatusText = computed(() => {
|
|
|
|
|
const status = userRSVP.value?.rsvp_status;
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'confirmed': return 'You are attending this event';
|
|
|
|
|
case 'waitlist': return 'You are on the waitlist';
|
|
|
|
|
case 'declined': return 'You declined this event';
|
|
|
|
|
default: return 'Status unknown';
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const showPaymentDetails = computed(() => {
|
|
|
|
|
return props.event?.is_paid === 'true' &&
|
|
|
|
|
userRSVP.value?.rsvp_status === 'confirmed' &&
|
|
|
|
|
userRSVP.value?.payment_status === 'pending';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const paymentAmount = computed(() => {
|
|
|
|
|
if (!userRSVP.value || !props.event) return '0';
|
|
|
|
|
|
|
|
|
|
const isMemberPricing = userRSVP.value.is_member_pricing === 'true';
|
|
|
|
|
return isMemberPricing ? props.event.cost_members : props.event.cost_non_members;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const paymentInfo = computed(() => ({
|
|
|
|
|
iban: 'FR76 1234 5678 9012 3456 7890 123', // This should come from config
|
|
|
|
|
recipient: 'MonacoUSA Association' // This should come from config
|
|
|
|
|
}));
|
|
|
|
|
|
2025-08-13 15:14:43 +02:00
|
|
|
// Guest functionality
|
|
|
|
|
const allowsGuests = computed(() => {
|
|
|
|
|
return props.event?.guests_permitted === 'true';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const maxGuestsAllowed = computed(() => {
|
|
|
|
|
if (!allowsGuests.value) return 0;
|
|
|
|
|
return parseInt(props.event?.max_guests_permitted || '0');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const guestOptions = computed(() => {
|
|
|
|
|
const max = maxGuestsAllowed.value;
|
|
|
|
|
const options = [];
|
|
|
|
|
for (let i = 0; i <= max; i++) {
|
|
|
|
|
options.push({
|
|
|
|
|
title: i === 0 ? 'No additional guests' : `${i} guest${i > 1 ? 's' : ''}`,
|
|
|
|
|
value: i
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return options;
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-12 04:25:35 +02:00
|
|
|
// Methods
|
|
|
|
|
const close = () => {
|
|
|
|
|
show.value = false;
|
|
|
|
|
rsvpNotes.value = '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const submitRSVP = async (status: 'confirmed' | 'declined') => {
|
|
|
|
|
if (!props.event) return;
|
|
|
|
|
|
|
|
|
|
rsvpLoading.value = true;
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-13 15:14:43 +02:00
|
|
|
// Use event_id field for consistent RSVP relationships
|
|
|
|
|
// This ensures RSVPs are linked properly to events using the business identifier
|
|
|
|
|
const eventId = props.event.event_id ||
|
|
|
|
|
(props.event as any).extendedProps?.event_id ||
|
|
|
|
|
(props.event as any).Id || // Fallback to database ID if event_id not available
|
|
|
|
|
props.event.id;
|
|
|
|
|
|
|
|
|
|
console.log('[EventDetailsDialog] Using event identifier for RSVP:', eventId);
|
2025-08-13 14:14:58 +02:00
|
|
|
console.log('[EventDetailsDialog] Event object keys:', Object.keys(props.event));
|
2025-08-13 15:14:43 +02:00
|
|
|
console.log('[EventDetailsDialog] Event event_id field:', props.event.event_id);
|
|
|
|
|
console.log('[EventDetailsDialog] Event database Id field:', (props.event as any).Id);
|
2025-08-13 14:14:58 +02:00
|
|
|
|
2025-08-13 15:14:43 +02:00
|
|
|
if (!eventId) {
|
|
|
|
|
throw new Error('Unable to determine event identifier');
|
2025-08-13 14:14:58 +02:00
|
|
|
}
|
2025-08-13 14:02:29 +02:00
|
|
|
|
2025-08-13 15:14:43 +02:00
|
|
|
await rsvpToEvent(eventId, {
|
2025-08-12 04:25:35 +02:00
|
|
|
member_id: '', // This will be filled by the composable
|
|
|
|
|
rsvp_status: status,
|
2025-08-13 15:14:43 +02:00
|
|
|
rsvp_notes: rsvpNotes.value,
|
|
|
|
|
extra_guests: selectedGuests.value.toString()
|
2025-08-12 04:25:35 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
emit('rsvp-updated', props.event);
|
|
|
|
|
// TODO: Show success message
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error submitting RSVP:', error);
|
|
|
|
|
// TODO: Show error message
|
|
|
|
|
} finally {
|
|
|
|
|
rsvpLoading.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const changeRSVP = () => {
|
|
|
|
|
// For now, just allow re-submitting RSVP
|
|
|
|
|
// In the future, this could open an edit dialog
|
|
|
|
|
if (userRSVP.value?.rsvp_status === 'confirmed') {
|
|
|
|
|
submitRSVP('declined');
|
|
|
|
|
} else if (userRSVP.value?.rsvp_status === 'declined') {
|
|
|
|
|
submitRSVP('confirmed');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const copyPaymentDetails = async () => {
|
|
|
|
|
const details = `
|
|
|
|
|
Event: ${props.event?.title}
|
|
|
|
|
Amount: €${paymentAmount.value}
|
|
|
|
|
IBAN: ${paymentInfo.value.iban}
|
|
|
|
|
Recipient: ${paymentInfo.value.recipient}
|
|
|
|
|
Reference: ${userRSVP.value?.payment_reference}
|
|
|
|
|
`.trim();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await navigator.clipboard.writeText(details);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error copying to clipboard:', error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.v-card {
|
|
|
|
|
max-height: 90vh;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.text-medium-emphasis {
|
|
|
|
|
opacity: 0.7;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.v-progress-linear {
|
|
|
|
|
max-width: 200px;
|
|
|
|
|
}
|
2025-08-13 13:02:12 +02:00
|
|
|
|
|
|
|
|
/* Rich text content styling */
|
|
|
|
|
.rich-text-content {
|
|
|
|
|
word-wrap: break-word;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(h1),
|
|
|
|
|
.rich-text-content :deep(h2),
|
|
|
|
|
.rich-text-content :deep(h3) {
|
|
|
|
|
color: rgb(var(--v-theme-on-surface));
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
margin: 16px 0 8px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(h1) {
|
|
|
|
|
font-size: 1.5rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(h2) {
|
|
|
|
|
font-size: 1.25rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(h3) {
|
|
|
|
|
font-size: 1.125rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(p) {
|
|
|
|
|
margin: 8px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(ul),
|
|
|
|
|
.rich-text-content :deep(ol) {
|
|
|
|
|
padding-left: 20px;
|
|
|
|
|
margin: 8px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(li) {
|
|
|
|
|
margin: 4px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(strong) {
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(em) {
|
|
|
|
|
font-style: italic;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(u) {
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(a) {
|
|
|
|
|
color: rgb(var(--v-theme-primary));
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rich-text-content :deep(a:hover) {
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
2025-08-12 04:25:35 +02:00
|
|
|
</style>
|