770 lines
22 KiB
Vue
770 lines
22 KiB
Vue
<template>
|
|
<v-dialog v-model="show" max-width="800" persistent>
|
|
<v-card>
|
|
<v-card-title class="d-flex justify-space-between align-center">
|
|
<div class="d-flex align-center">
|
|
<v-icon class="me-2">mdi-calendar-plus</v-icon>
|
|
<span>Create New Event</span>
|
|
</div>
|
|
<v-btn
|
|
@click="close"
|
|
icon
|
|
variant="text"
|
|
size="small"
|
|
>
|
|
<v-icon>mdi-close</v-icon>
|
|
</v-btn>
|
|
</v-card-title>
|
|
|
|
<v-card-text>
|
|
<v-form ref="form" v-model="valid" @submit.prevent="handleSubmit">
|
|
<v-row>
|
|
<!-- Basic Information -->
|
|
<v-col cols="12">
|
|
<v-text-field
|
|
v-model="eventData.title"
|
|
label="Event Title*"
|
|
:rules="[v => !!v || 'Title is required']"
|
|
variant="outlined"
|
|
required
|
|
/>
|
|
</v-col>
|
|
|
|
<v-col cols="12">
|
|
<VuetifyTiptap
|
|
v-model="eventData.description"
|
|
label="Description"
|
|
:toolbar="[
|
|
'bold',
|
|
'italic',
|
|
'underline',
|
|
'|',
|
|
'heading',
|
|
'|',
|
|
'bulletList',
|
|
'orderedList',
|
|
'|',
|
|
'link',
|
|
'|',
|
|
'undo',
|
|
'redo'
|
|
]"
|
|
:max-height="200"
|
|
placeholder="Enter event description with formatting..."
|
|
outlined
|
|
/>
|
|
</v-col>
|
|
|
|
<!-- Event Type and Visibility -->
|
|
<v-col cols="12" md="6">
|
|
<v-select
|
|
v-model="eventData.event_type"
|
|
:items="eventTypes"
|
|
label="Event Type*"
|
|
:rules="[v => !!v || 'Event type is required']"
|
|
variant="outlined"
|
|
required
|
|
/>
|
|
</v-col>
|
|
|
|
<v-col cols="12" md="6">
|
|
<v-select
|
|
v-model="eventData.visibility"
|
|
:items="visibilityOptions"
|
|
label="Visibility*"
|
|
:rules="[v => !!v || 'Visibility is required']"
|
|
variant="outlined"
|
|
required
|
|
/>
|
|
</v-col>
|
|
|
|
<!-- Date and Time -->
|
|
<v-col cols="12" md="6">
|
|
<VDateInput
|
|
v-model="startDate"
|
|
label="Start Date*"
|
|
:rules="[
|
|
v => !!v || 'Start date is required',
|
|
v => !v || new Date(v).getTime() >= new Date().setHours(0,0,0,0) || 'Start date cannot be in the past'
|
|
]"
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-calendar"
|
|
required
|
|
:min="new Date().toISOString().split('T')[0]"
|
|
/>
|
|
</v-col>
|
|
|
|
<v-col cols="12" md="6">
|
|
<v-text-field
|
|
v-model="startTime"
|
|
label="Start Time*"
|
|
type="time"
|
|
:rules="[v => !!v || 'Start time is required']"
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-clock"
|
|
required
|
|
/>
|
|
</v-col>
|
|
|
|
<v-col cols="12" md="6">
|
|
<VDateInput
|
|
v-model="endDate"
|
|
label="End Date*"
|
|
:rules="[
|
|
v => !!v || 'End date is required',
|
|
v => !v || new Date(v).getTime() >= new Date().setHours(0,0,0,0) || 'End date cannot be in the past',
|
|
v => !v || !startDate || new Date(v).getTime() >= new Date(startDate).getTime() || 'End date must be same or after start date'
|
|
]"
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-calendar"
|
|
:min="startDate || new Date().toISOString().split('T')[0]"
|
|
required
|
|
/>
|
|
</v-col>
|
|
|
|
<v-col cols="12" md="6">
|
|
<v-text-field
|
|
v-model="endTime"
|
|
label="End Time*"
|
|
type="time"
|
|
:rules="[
|
|
v => !!v || 'End time is required',
|
|
v => !validateEndTime() || 'End time must be after start time when on same date'
|
|
]"
|
|
variant="outlined"
|
|
prepend-inner-icon="mdi-clock"
|
|
required
|
|
/>
|
|
</v-col>
|
|
|
|
<!-- Location -->
|
|
<v-col cols="12">
|
|
<v-text-field
|
|
v-model="eventData.location"
|
|
label="Location"
|
|
variant="outlined"
|
|
/>
|
|
</v-col>
|
|
|
|
<!-- Capacity Settings -->
|
|
<v-col cols="12" md="6">
|
|
<v-text-field
|
|
v-model="eventData.max_attendees"
|
|
label="Maximum Attendees"
|
|
type="number"
|
|
variant="outlined"
|
|
hint="Leave empty for unlimited capacity"
|
|
persistent-hint
|
|
/>
|
|
</v-col>
|
|
|
|
<!-- Guest Settings -->
|
|
<v-col cols="12" md="6">
|
|
<v-switch
|
|
v-model="allowGuests"
|
|
label="Allow Guests"
|
|
color="primary"
|
|
inset
|
|
hint="Members can bring additional guests"
|
|
persistent-hint
|
|
/>
|
|
</v-col>
|
|
|
|
<!-- Max Guests Per Person (shown when guests allowed) -->
|
|
<v-col v-if="allowGuests" cols="12" md="6">
|
|
<v-text-field
|
|
v-model="maxGuestsPerPerson"
|
|
label="Max Guests Per Person"
|
|
type="number"
|
|
variant="outlined"
|
|
:rules="allowGuests ? [v => v && parseInt(v) > 0 || 'Must allow at least 1 guest'] : []"
|
|
hint="Maximum additional guests each member can bring"
|
|
persistent-hint
|
|
/>
|
|
</v-col>
|
|
|
|
<!-- Payment Settings -->
|
|
<v-col cols="12" :md="allowGuests ? 6 : 6">
|
|
<v-switch
|
|
v-model="isPaidEvent"
|
|
label="Paid Event"
|
|
color="primary"
|
|
inset
|
|
/>
|
|
</v-col>
|
|
|
|
<!-- Payment Details (shown when paid event) -->
|
|
<template v-if="isPaidEvent">
|
|
<v-col cols="12" md="6">
|
|
<v-text-field
|
|
v-model="eventData.cost_members"
|
|
label="Cost for Members (€)"
|
|
type="number"
|
|
step="0.01"
|
|
variant="outlined"
|
|
:rules="isPaidEvent ? [v => !!v || 'Member cost is required'] : []"
|
|
/>
|
|
</v-col>
|
|
|
|
<v-col cols="12" md="6">
|
|
<v-text-field
|
|
v-model="eventData.cost_non_members"
|
|
label="Cost for Non-Members (€)"
|
|
type="number"
|
|
step="0.01"
|
|
variant="outlined"
|
|
:rules="isPaidEvent ? [v => !!v || 'Non-member cost is required'] : []"
|
|
/>
|
|
</v-col>
|
|
|
|
<v-col cols="12">
|
|
<v-switch
|
|
v-model="memberPricingEnabled"
|
|
label="Enable Member Pricing"
|
|
color="primary"
|
|
inset
|
|
hint="Allow current members to pay member rates"
|
|
persistent-hint
|
|
/>
|
|
</v-col>
|
|
</template>
|
|
|
|
<!-- Advanced Options -->
|
|
<v-col cols="12">
|
|
<v-expansion-panels variant="accordion">
|
|
<v-expansion-panel>
|
|
<v-expansion-panel-title>
|
|
<v-icon start>mdi-cog</v-icon>
|
|
Advanced Options
|
|
</v-expansion-panel-title>
|
|
<v-expansion-panel-text>
|
|
<v-row>
|
|
<v-col cols="12" md="6">
|
|
<v-switch
|
|
v-model="isRecurring"
|
|
label="Recurring Event"
|
|
color="primary"
|
|
inset
|
|
hint="Create a series of events"
|
|
persistent-hint
|
|
/>
|
|
</v-col>
|
|
|
|
<v-col v-if="isRecurring" cols="12" md="6">
|
|
<v-select
|
|
v-model="recurrenceFrequency"
|
|
:items="recurrenceOptions"
|
|
label="Frequency"
|
|
variant="outlined"
|
|
/>
|
|
</v-col>
|
|
|
|
<v-col cols="12" md="6">
|
|
<v-select
|
|
v-model="eventData.status"
|
|
:items="statusOptions"
|
|
label="Status"
|
|
variant="outlined"
|
|
/>
|
|
</v-col>
|
|
</v-row>
|
|
</v-expansion-panel-text>
|
|
</v-expansion-panel>
|
|
</v-expansion-panels>
|
|
</v-col>
|
|
</v-row>
|
|
</v-form>
|
|
</v-card-text>
|
|
|
|
<!-- Error message display -->
|
|
<v-card-text v-if="errorMessage" class="pt-0">
|
|
<v-alert
|
|
type="error"
|
|
variant="tonal"
|
|
closable
|
|
@click:close="errorMessage = null"
|
|
>
|
|
{{ errorMessage }}
|
|
</v-alert>
|
|
</v-card-text>
|
|
|
|
<v-card-actions class="pa-4">
|
|
<v-spacer />
|
|
<v-btn
|
|
@click="close"
|
|
variant="outlined"
|
|
:disabled="loading"
|
|
>
|
|
Cancel
|
|
</v-btn>
|
|
<v-btn
|
|
@click="handleSubmit"
|
|
color="primary"
|
|
:loading="loading"
|
|
:disabled="!valid"
|
|
>
|
|
Create Event
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { EventCreateRequest } from '~/utils/types';
|
|
import { useAuth } from '~/composables/useAuth';
|
|
import { useEvents } from '~/composables/useEvents';
|
|
|
|
interface Props {
|
|
modelValue: boolean;
|
|
prefilledDate?: string;
|
|
prefilledEndDate?: string;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
modelValue: false,
|
|
prefilledDate: undefined,
|
|
prefilledEndDate: undefined
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: boolean];
|
|
'event-created': [event: any];
|
|
}>();
|
|
|
|
const { isAdmin } = useAuth();
|
|
const { createEvent } = useEvents();
|
|
|
|
// Reactive state
|
|
const form = ref();
|
|
const valid = ref(false);
|
|
const loading = ref(false);
|
|
const isPaidEvent = ref(false);
|
|
const memberPricingEnabled = ref(true);
|
|
const isRecurring = ref(false);
|
|
const recurrenceFrequency = ref('weekly');
|
|
|
|
// Date and time picker state
|
|
const startDate = ref<string>('');
|
|
const startTime = ref<string>('');
|
|
const endDate = ref<string>('');
|
|
const endTime = ref<string>('');
|
|
|
|
// Legacy date model refs for backward compatibility
|
|
const startDateModel = ref<Date | null>(null);
|
|
const endDateModel = ref<Date | null>(null);
|
|
|
|
// Date format for display
|
|
const dateTimeFormat = 'dd/MM/yyyy HH:mm (Monaco)';
|
|
|
|
// Form data
|
|
const eventData = reactive<EventCreateRequest>({
|
|
title: '',
|
|
description: '',
|
|
event_type: 'social',
|
|
start_datetime: '',
|
|
end_datetime: '',
|
|
location: '',
|
|
max_attendees: '',
|
|
is_paid: 'false',
|
|
cost_members: '',
|
|
cost_non_members: '',
|
|
member_pricing_enabled: 'true',
|
|
visibility: 'public',
|
|
status: 'active',
|
|
guests_permitted: 'false',
|
|
max_guests_permitted: '0'
|
|
});
|
|
|
|
// Guest settings
|
|
const allowGuests = ref(false);
|
|
const maxGuestsPerPerson = ref(1);
|
|
|
|
// Computed
|
|
const show = computed({
|
|
get: () => props.modelValue,
|
|
set: (value) => emit('update:modelValue', value)
|
|
});
|
|
|
|
// Options
|
|
const eventTypes = [
|
|
{ title: 'Social Event', value: 'social' },
|
|
{ title: 'Meeting', value: 'meeting' },
|
|
{ title: 'Fundraiser', value: 'fundraiser' },
|
|
{ title: 'Workshop', value: 'workshop' },
|
|
{ title: 'Board Only', value: 'board-only' }
|
|
];
|
|
|
|
const visibilityOptions = computed(() => {
|
|
const options = [
|
|
{ title: 'Public', value: 'public' },
|
|
{ title: 'Board Only', value: 'board-only' }
|
|
];
|
|
|
|
if (isAdmin.value) {
|
|
options.push({ title: 'Admin Only', value: 'admin-only' });
|
|
}
|
|
|
|
return options;
|
|
});
|
|
|
|
const statusOptions = [
|
|
{ title: 'Active', value: 'active' },
|
|
{ title: 'Draft', value: 'draft' }
|
|
];
|
|
|
|
const recurrenceOptions = [
|
|
{ title: 'Weekly', value: 'weekly' },
|
|
{ title: 'Monthly', value: 'monthly' },
|
|
{ title: 'Yearly', value: 'yearly' }
|
|
];
|
|
|
|
// Watchers
|
|
watch(isPaidEvent, (newValue) => {
|
|
eventData.is_paid = newValue ? 'true' : 'false';
|
|
});
|
|
|
|
watch(memberPricingEnabled, (newValue) => {
|
|
eventData.member_pricing_enabled = newValue ? 'true' : 'false';
|
|
});
|
|
|
|
watch(allowGuests, (newValue) => {
|
|
eventData.guests_permitted = newValue ? 'true' : 'false';
|
|
if (!newValue) {
|
|
eventData.max_guests_permitted = '0';
|
|
maxGuestsPerPerson.value = 1;
|
|
}
|
|
});
|
|
|
|
watch(maxGuestsPerPerson, (newValue) => {
|
|
if (allowGuests.value) {
|
|
eventData.max_guests_permitted = newValue.toString();
|
|
}
|
|
});
|
|
|
|
// Fix date picker binding - ensure proper syncing
|
|
watch(startDateModel, (newDate) => {
|
|
if (newDate instanceof Date) {
|
|
eventData.start_datetime = newDate.toISOString();
|
|
console.log('[CreateEventDialog] Start date updated:', eventData.start_datetime);
|
|
}
|
|
});
|
|
|
|
watch(endDateModel, (newDate) => {
|
|
if (newDate instanceof Date) {
|
|
eventData.end_datetime = newDate.toISOString();
|
|
console.log('[CreateEventDialog] End date updated:', eventData.end_datetime);
|
|
}
|
|
});
|
|
|
|
watch(isRecurring, (newValue) => {
|
|
eventData.is_recurring = newValue ? 'true' : 'false';
|
|
if (newValue) {
|
|
eventData.recurrence_pattern = JSON.stringify({
|
|
frequency: recurrenceFrequency.value,
|
|
interval: 1,
|
|
end_date: null
|
|
});
|
|
} else {
|
|
eventData.recurrence_pattern = '';
|
|
}
|
|
});
|
|
|
|
watch(recurrenceFrequency, (newValue) => {
|
|
if (isRecurring.value) {
|
|
eventData.recurrence_pattern = JSON.stringify({
|
|
frequency: newValue,
|
|
interval: 1,
|
|
end_date: null
|
|
});
|
|
}
|
|
});
|
|
|
|
// Watch for separate date and time changes to combine them
|
|
watch([startDate, startTime], ([newDate, newTime]) => {
|
|
if (newDate && newTime) {
|
|
const combinedDateTime = new Date(`${newDate}T${newTime}`);
|
|
eventData.start_datetime = combinedDateTime.toISOString();
|
|
console.log('[CreateEventDialog] Combined start datetime:', eventData.start_datetime);
|
|
}
|
|
});
|
|
|
|
watch([endDate, endTime], ([newDate, newTime]) => {
|
|
if (newDate && newTime) {
|
|
const combinedDateTime = new Date(`${newDate}T${newTime}`);
|
|
eventData.end_datetime = combinedDateTime.toISOString();
|
|
console.log('[CreateEventDialog] Combined end datetime:', eventData.end_datetime);
|
|
}
|
|
});
|
|
|
|
// Watch for prefilled dates
|
|
watch(() => props.prefilledDate, (newDate) => {
|
|
if (newDate) {
|
|
const prefillDate = new Date(newDate);
|
|
startDate.value = prefillDate.toISOString().split('T')[0];
|
|
startTime.value = prefillDate.toTimeString().substring(0, 5);
|
|
|
|
// Set end date 2 hours later if not provided
|
|
if (!props.prefilledEndDate) {
|
|
const endDateTime = new Date(prefillDate);
|
|
endDateTime.setHours(endDateTime.getHours() + 2);
|
|
endDate.value = endDateTime.toISOString().split('T')[0];
|
|
endTime.value = endDateTime.toTimeString().substring(0, 5);
|
|
}
|
|
}
|
|
}, { immediate: true });
|
|
|
|
watch(() => props.prefilledEndDate, (newEndDate) => {
|
|
if (newEndDate) {
|
|
const prefillEndDate = new Date(newEndDate);
|
|
endDate.value = prefillEndDate.toISOString().split('T')[0];
|
|
endTime.value = prefillEndDate.toTimeString().substring(0, 5);
|
|
}
|
|
}, { immediate: true });
|
|
|
|
// Date picker handlers
|
|
const handleStartDateUpdate = (date: Date | null) => {
|
|
if (date) {
|
|
eventData.start_datetime = date.toISOString();
|
|
}
|
|
};
|
|
|
|
const handleEndDateUpdate = (date: Date | null) => {
|
|
if (date) {
|
|
eventData.end_datetime = date.toISOString();
|
|
}
|
|
};
|
|
|
|
const onDatePickerClosed = () => {
|
|
console.log('[CreateEventDialog] Date picker closed');
|
|
// This handler ensures the date picker behaves correctly on mobile and desktop
|
|
};
|
|
|
|
// Validation functions
|
|
const validateEndTime = () => {
|
|
if (!startDate.value || !endDate.value || !startTime.value || !endTime.value) {
|
|
return false; // Return false (no error) if not all fields are filled
|
|
}
|
|
|
|
// Only validate if start and end are on the same date
|
|
if (startDate.value === endDate.value) {
|
|
const start = startTime.value;
|
|
const end = endTime.value;
|
|
return start >= end; // Return true if there's an error (start >= end)
|
|
}
|
|
|
|
return false; // No error if different dates
|
|
};
|
|
|
|
// Methods
|
|
const resetForm = () => {
|
|
eventData.title = '';
|
|
eventData.description = '';
|
|
eventData.event_type = 'social';
|
|
eventData.start_datetime = '';
|
|
eventData.end_datetime = '';
|
|
eventData.location = '';
|
|
eventData.max_attendees = '';
|
|
eventData.is_paid = 'false';
|
|
eventData.cost_members = '';
|
|
eventData.cost_non_members = '';
|
|
eventData.member_pricing_enabled = 'true';
|
|
eventData.guests_permitted = 'false';
|
|
eventData.max_guests_permitted = '0';
|
|
eventData.visibility = 'public';
|
|
eventData.status = 'active';
|
|
eventData.is_recurring = 'false';
|
|
eventData.recurrence_pattern = '';
|
|
|
|
// Reset date pickers
|
|
startDateModel.value = null;
|
|
endDateModel.value = null;
|
|
|
|
// Reset UI state
|
|
isPaidEvent.value = false;
|
|
memberPricingEnabled.value = true;
|
|
isRecurring.value = false;
|
|
recurrenceFrequency.value = 'weekly';
|
|
allowGuests.value = false;
|
|
maxGuestsPerPerson.value = 1;
|
|
|
|
form.value?.resetValidation();
|
|
};
|
|
|
|
const close = () => {
|
|
show.value = false;
|
|
resetForm();
|
|
};
|
|
|
|
// Error handling
|
|
const errorMessage = ref<string | null>(null);
|
|
|
|
const handleSubmit = async () => {
|
|
if (!form.value) return;
|
|
|
|
const isValid = await form.value.validate();
|
|
if (!isValid.valid) return;
|
|
|
|
// Clear previous errors
|
|
errorMessage.value = null;
|
|
|
|
// Validate that we have proper date/time combination
|
|
if (!startDate.value || !startTime.value) {
|
|
errorMessage.value = 'Start date and time are required';
|
|
return;
|
|
}
|
|
|
|
if (!endDate.value || !endTime.value) {
|
|
errorMessage.value = 'End date and time are required';
|
|
return;
|
|
}
|
|
|
|
loading.value = true;
|
|
|
|
try {
|
|
// Combine date and time properly
|
|
const startDateTime = new Date(`${startDate.value}T${startTime.value}`);
|
|
const endDateTime = new Date(`${endDate.value}T${endTime.value}`);
|
|
|
|
// Validate start is not in the past
|
|
if (startDateTime < new Date()) {
|
|
errorMessage.value = 'Event start time cannot be in the past';
|
|
loading.value = false;
|
|
return;
|
|
}
|
|
|
|
// Validate end is after start
|
|
if (endDateTime <= startDateTime) {
|
|
errorMessage.value = 'Event end time must be after start time';
|
|
loading.value = false;
|
|
return;
|
|
}
|
|
|
|
const formattedEventData = {
|
|
...eventData,
|
|
start_datetime: startDateTime.toISOString(),
|
|
end_datetime: endDateTime.toISOString()
|
|
};
|
|
|
|
console.log('[CreateEventDialog] Creating event with data:', formattedEventData);
|
|
|
|
const newEvent = await createEvent(formattedEventData);
|
|
|
|
emit('event-created', newEvent);
|
|
close();
|
|
} catch (error: any) {
|
|
console.error('Error creating event:', error);
|
|
|
|
// Parse error message for better UX
|
|
let userErrorMessage = 'Failed to create event';
|
|
|
|
if (error?.data?.message) {
|
|
userErrorMessage = error.data.message;
|
|
} else if (error?.message) {
|
|
if (error.message.includes('past')) {
|
|
userErrorMessage = 'Event date cannot be in the past';
|
|
} else if (error.message.includes('validation')) {
|
|
userErrorMessage = 'Please check all required fields';
|
|
} else {
|
|
userErrorMessage = error.message;
|
|
}
|
|
}
|
|
|
|
errorMessage.value = userErrorMessage;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
// Removed duplicate prefilled date logic - handled by watchers above
|
|
</script>
|
|
|
|
<style scoped>
|
|
.v-card {
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.v-expansion-panel-title {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.v-switch {
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.v-text-field :deep(.v-field__input) {
|
|
min-height: 56px;
|
|
}
|
|
|
|
/* Date picker styling to match Vuetify */
|
|
.date-picker-wrapper {
|
|
width: 100%;
|
|
}
|
|
|
|
.date-picker-label {
|
|
font-size: 16px;
|
|
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
|
|
font-weight: 400;
|
|
line-height: 1.5;
|
|
letter-spacing: 0.009375em;
|
|
margin-bottom: 8px;
|
|
display: block;
|
|
}
|
|
|
|
/* Style the Vue DatePicker to match Vuetify inputs */
|
|
:deep(.dp__input) {
|
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
border-radius: 4px;
|
|
padding: 16px 12px;
|
|
padding-right: 48px; /* Make room for calendar icon */
|
|
font-size: 16px;
|
|
line-height: 1.5;
|
|
background: rgb(var(--v-theme-surface));
|
|
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
|
transition: border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
width: 100%;
|
|
min-height: 56px;
|
|
}
|
|
|
|
:deep(.dp__input:hover) {
|
|
border-color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
|
|
}
|
|
|
|
:deep(.dp__input:focus) {
|
|
border-color: rgb(var(--v-theme-primary));
|
|
border-width: 2px;
|
|
outline: none;
|
|
}
|
|
|
|
:deep(.dp__input_readonly) {
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Style the date picker dropdown */
|
|
:deep(.dp__menu) {
|
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
border-radius: 4px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
background: rgb(var(--v-theme-surface));
|
|
}
|
|
|
|
/* Primary color theming for the date picker */
|
|
:deep(.dp__primary_color) {
|
|
background-color: rgb(var(--v-theme-primary));
|
|
}
|
|
|
|
:deep(.dp__primary_text) {
|
|
color: rgb(var(--v-theme-primary));
|
|
}
|
|
|
|
:deep(.dp__active_date) {
|
|
background-color: rgb(var(--v-theme-primary));
|
|
color: rgb(var(--v-theme-on-primary));
|
|
}
|
|
|
|
:deep(.dp__today) {
|
|
border: 1px solid rgb(var(--v-theme-primary));
|
|
}
|
|
</style>
|