Feat: Agregar soporte para envío de Contacts, Polls y Events
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m8s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m8s
- Backend: Nuevo soporte en endpoint /send para tipos contact, poll, event - UI: Modales para crear y enviar contactos, encuestas y eventos - Visualización: Componentes MessagePoll y MessageEvent para mostrar mensajes recibidos - Tipos: Agregar PollInfo, EventInfo y tipo 'event' a MessageType
This commit is contained in:
257
app/components/messages/EventSendModal.vue
Normal file
257
app/components/messages/EventSendModal.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<UModal v-model:open="isOpen">
|
||||
<template #content>
|
||||
<UCard>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-lg font-semibold text-white">Crear Evento</h3>
|
||||
<UButton
|
||||
variant="ghost"
|
||||
icon="i-lucide-x"
|
||||
size="sm"
|
||||
@click="isOpen = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="space-y-4 max-h-[60vh] overflow-y-auto">
|
||||
<!-- Event name -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--wa-text-muted)] mb-1">
|
||||
Nombre del evento *
|
||||
</label>
|
||||
<UInput
|
||||
v-model="eventName"
|
||||
placeholder="Reunión de equipo"
|
||||
icon="i-lucide-calendar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Start date/time -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--wa-text-muted)] mb-1">
|
||||
Fecha inicio *
|
||||
</label>
|
||||
<UInput
|
||||
v-model="startDate"
|
||||
type="date"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--wa-text-muted)] mb-1">
|
||||
Hora inicio *
|
||||
</label>
|
||||
<UInput
|
||||
v-model="startTime"
|
||||
type="time"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- End date/time (optional) -->
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<UCheckbox v-model="hasEndDate" />
|
||||
<span class="text-sm text-[var(--wa-text-muted)]">Agregar fecha de fin</span>
|
||||
</div>
|
||||
|
||||
<div v-if="hasEndDate" class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--wa-text-muted)] mb-1">
|
||||
Fecha fin
|
||||
</label>
|
||||
<UInput
|
||||
v-model="endDate"
|
||||
type="date"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--wa-text-muted)] mb-1">
|
||||
Hora fin
|
||||
</label>
|
||||
<UInput
|
||||
v-model="endTime"
|
||||
type="time"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-[var(--wa-text-muted)] mb-1">
|
||||
Descripción (opcional)
|
||||
</label>
|
||||
<UTextarea
|
||||
v-model="description"
|
||||
placeholder="Detalles del evento..."
|
||||
:rows="2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<UCheckbox v-model="hasLocation" />
|
||||
<span class="text-sm text-[var(--wa-text-muted)]">Agregar ubicación</span>
|
||||
</div>
|
||||
|
||||
<div v-if="hasLocation" class="space-y-3 p-3 rounded-lg bg-[var(--wa-bg-light)]">
|
||||
<UInput
|
||||
v-model="locationName"
|
||||
placeholder="Nombre del lugar"
|
||||
icon="i-lucide-map-pin"
|
||||
/>
|
||||
<UInput
|
||||
v-model="locationAddress"
|
||||
placeholder="Dirección"
|
||||
icon="i-lucide-navigation"
|
||||
/>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<UInput
|
||||
v-model.number="latitude"
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="Latitud"
|
||||
/>
|
||||
<UInput
|
||||
v-model.number="longitude"
|
||||
type="number"
|
||||
step="any"
|
||||
placeholder="Longitud"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-xs text-[var(--wa-text-muted)]">
|
||||
Las coordenadas son opcionales pero permiten mostrar el mapa
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-2">
|
||||
<UButton variant="ghost" @click="isOpen = false">
|
||||
Cancelar
|
||||
</UButton>
|
||||
<UButton
|
||||
:disabled="!isValid"
|
||||
:loading="isSending"
|
||||
@click="handleSend"
|
||||
>
|
||||
Crear evento
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UCard>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface EventData {
|
||||
name: string
|
||||
startDate: string
|
||||
endDate?: string
|
||||
description?: string
|
||||
location?: {
|
||||
name?: string
|
||||
address?: string
|
||||
latitude?: number
|
||||
longitude?: number
|
||||
}
|
||||
}
|
||||
|
||||
const isOpen = defineModel<boolean>('open', { default: false })
|
||||
|
||||
const emit = defineEmits<{
|
||||
send: [event: EventData]
|
||||
}>()
|
||||
|
||||
const isSending = ref(false)
|
||||
|
||||
// Form fields
|
||||
const eventName = ref('')
|
||||
const startDate = ref('')
|
||||
const startTime = ref('')
|
||||
const hasEndDate = ref(false)
|
||||
const endDate = ref('')
|
||||
const endTime = ref('')
|
||||
const description = ref('')
|
||||
const hasLocation = ref(false)
|
||||
const locationName = ref('')
|
||||
const locationAddress = ref('')
|
||||
const latitude = ref<number | undefined>(undefined)
|
||||
const longitude = ref<number | undefined>(undefined)
|
||||
|
||||
const isValid = computed(() => {
|
||||
return eventName.value.trim() && startDate.value && startTime.value
|
||||
})
|
||||
|
||||
const handleSend = async () => {
|
||||
if (!isValid.value) return
|
||||
|
||||
isSending.value = true
|
||||
try {
|
||||
const startDateTime = new Date(`${startDate.value}T${startTime.value}`)
|
||||
|
||||
const eventData: EventData = {
|
||||
name: eventName.value.trim(),
|
||||
startDate: startDateTime.toISOString()
|
||||
}
|
||||
|
||||
if (hasEndDate.value && endDate.value && endTime.value) {
|
||||
const endDateTime = new Date(`${endDate.value}T${endTime.value}`)
|
||||
eventData.endDate = endDateTime.toISOString()
|
||||
}
|
||||
|
||||
if (description.value.trim()) {
|
||||
eventData.description = description.value.trim()
|
||||
}
|
||||
|
||||
if (hasLocation.value) {
|
||||
eventData.location = {}
|
||||
if (locationName.value.trim()) {
|
||||
eventData.location.name = locationName.value.trim()
|
||||
}
|
||||
if (locationAddress.value.trim()) {
|
||||
eventData.location.address = locationAddress.value.trim()
|
||||
}
|
||||
if (latitude.value !== undefined && longitude.value !== undefined) {
|
||||
eventData.location.latitude = latitude.value
|
||||
eventData.location.longitude = longitude.value
|
||||
}
|
||||
}
|
||||
|
||||
emit('send', eventData)
|
||||
|
||||
// Reset form
|
||||
resetForm()
|
||||
isOpen.value = false
|
||||
} finally {
|
||||
isSending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
eventName.value = ''
|
||||
startDate.value = ''
|
||||
startTime.value = ''
|
||||
hasEndDate.value = false
|
||||
endDate.value = ''
|
||||
endTime.value = ''
|
||||
description.value = ''
|
||||
hasLocation.value = false
|
||||
locationName.value = ''
|
||||
locationAddress.value = ''
|
||||
latitude.value = undefined
|
||||
longitude.value = undefined
|
||||
}
|
||||
|
||||
// Reset form when modal opens
|
||||
watch(isOpen, (open) => {
|
||||
if (open) {
|
||||
resetForm()
|
||||
// Set default start date to today
|
||||
const today = new Date()
|
||||
startDate.value = today.toISOString().split('T')[0]
|
||||
startTime.value = '12:00'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user