monacousa-portal/components/UpcomingEventBanner.vue

283 lines
6.8 KiB
Vue
Raw Normal View History

<template>
<v-banner
v-if="event"
:color="bannerColor"
lines="two"
elevation="2"
rounded
>
<template #prepend>
<v-icon :color="iconColor" size="large">{{ eventIcon }}</v-icon>
</template>
<template #text>
<div class="d-flex flex-column">
<div class="text-h6 font-weight-bold mb-1">{{ event.title }}</div>
<div class="d-flex flex-wrap align-center ga-4 text-body-2">
<div class="d-flex align-center">
<v-icon size="small" class="me-1">mdi-calendar-clock</v-icon>
<span>{{ formatEventDate }}</span>
</div>
<div v-if="event.location" class="d-flex align-center">
<v-icon size="small" class="me-1">mdi-map-marker</v-icon>
<span>{{ event.location }}</span>
</div>
<div v-if="event.is_paid === 'true'" class="d-flex align-center">
<v-icon size="small" class="me-1">mdi-currency-eur</v-icon>
<span>{{ memberPrice }}</span>
</div>
<div v-if="capacityInfo" class="d-flex align-center">
<v-icon size="small" class="me-1">mdi-account-group</v-icon>
<span>{{ capacityInfo }}</span>
</div>
</div>
</div>
</template>
<template #actions>
<div class="d-flex flex-column flex-sm-row ga-2">
<!-- RSVP Status -->
<v-chip
v-if="userRSVP"
:color="rsvpStatusColor"
size="small"
variant="flat"
>
<v-icon start size="small">{{ rsvpStatusIcon }}</v-icon>
{{ rsvpStatusText }}
</v-chip>
<!-- Action Buttons -->
<v-btn
@click="handleViewEvent"
variant="elevated"
color="white"
size="small"
prepend-icon="mdi-eye"
>
View Details
</v-btn>
<v-btn
v-if="!userRSVP && canRSVP"
@click="handleQuickRSVP"
:color="quickRSVPColor"
size="small"
prepend-icon="mdi-check"
>
Quick RSVP
</v-btn>
</div>
</template>
</v-banner>
</template>
<script setup lang="ts">
import type { Event, EventRSVP } from '~/utils/types';
import { format, isWithinInterval, addDays } from 'date-fns';
interface Props {
event: Event | null;
}
const props = defineProps<Props>();
const emit = defineEmits<{
'event-click': [event: Event];
'quick-rsvp': [event: Event];
}>();
// Computed properties
const userRSVP = computed((): EventRSVP | null => {
return props.event?.user_rsvp || null;
});
const canRSVP = computed(() => {
if (!props.event) return false;
const eventDate = new Date(props.event.start_datetime);
const now = new Date();
return eventDate > now; // Can RSVP to future events
});
const eventIcon = computed(() => {
if (!props.event) return 'mdi-calendar';
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 bannerColor = computed(() => {
if (!props.event) return 'primary';
// Check if event is soon (within 24 hours)
const eventDate = new Date(props.event.start_datetime);
const now = new Date();
const isSoon = isWithinInterval(eventDate, {
start: now,
end: addDays(now, 1)
});
if (isSoon) return 'warning';
const colors = {
'meeting': 'blue',
'social': 'green',
'fundraiser': 'orange',
'workshop': 'purple',
'board-only': 'red'
};
return colors[props.event.event_type as keyof typeof colors] || 'primary';
});
const iconColor = computed(() => {
// Use white for better contrast on colored backgrounds
return 'white';
});
const formatEventDate = computed(() => {
if (!props.event) return '';
const startDate = new Date(props.event.start_datetime);
const endDate = new Date(props.event.end_datetime);
const now = new Date();
// Different formats based on timing
if (startDate.toDateString() === now.toDateString()) {
return `Today at ${format(startDate, 'HH:mm')}`;
}
if (startDate.toDateString() === addDays(now, 1).toDateString()) {
return `Tomorrow at ${format(startDate, 'HH:mm')}`;
}
if (startDate.toDateString() === endDate.toDateString()) {
return format(startDate, 'EEE, MMM d • HH:mm');
}
return `${format(startDate, 'MMM d')} - ${format(endDate, 'MMM d')}`;
});
const memberPrice = computed(() => {
if (!props.event || props.event.is_paid !== 'true') return '';
if (props.event.cost_members && props.event.cost_non_members) {
return `${props.event.cost_members} (Members)`;
}
return `${props.event.cost_members || props.event.cost_non_members}`;
});
const capacityInfo = computed(() => {
if (!props.event?.max_attendees) return '';
const current = props.event.current_attendees || 0;
const max = parseInt(props.event.max_attendees);
return `${current}/${max} attending`;
});
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';
case 'waitlist': return 'mdi-clock';
case 'declined': return 'mdi-close';
default: return 'mdi-help';
}
});
const rsvpStatusText = computed(() => {
const status = userRSVP.value?.rsvp_status;
switch (status) {
case 'confirmed': return 'Attending';
case 'waitlist': return 'Waitlisted';
case 'declined': return 'Declined';
default: return 'Unknown';
}
});
const quickRSVPColor = computed(() => {
return bannerColor.value === 'warning' ? 'success' : 'white';
});
// Methods
const handleViewEvent = () => {
if (props.event) {
emit('event-click', props.event);
}
};
const handleQuickRSVP = () => {
if (props.event) {
emit('quick-rsvp', props.event);
}
};
</script>
<style scoped>
.v-banner :deep(.v-banner__wrapper) {
padding: 16px 24px;
}
.v-banner :deep(.v-banner__prepend) {
margin-inline-end: 16px;
}
.v-banner :deep(.v-banner__actions) {
margin-inline-start: 16px;
}
/* Mobile optimizations */
@media (max-width: 600px) {
.v-banner :deep(.v-banner__wrapper) {
padding: 12px 16px;
}
.v-banner :deep(.v-banner__prepend) {
margin-inline-end: 12px;
}
.v-banner :deep(.v-banner__actions) {
margin-inline-start: 0;
margin-top: 8px;
}
.text-h6 {
font-size: 1.1rem !important;
}
}
/* Ensure proper spacing on different screen sizes */
.ga-4 {
gap: 16px;
}
.ga-2 {
gap: 8px;
}
@media (max-width: 600px) {
.ga-4 {
gap: 8px;
}
}
</style>