e5ec5bb6a0
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
327 lines
9.4 KiB
Vue
327 lines
9.4 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch } from 'vue'
|
|
import type { CollectionObject } from '@ChronoManager/models/collection'
|
|
import type { EntityObject } from '@ChronoManager/models/entity'
|
|
import type { EventObject } from '@ChronoManager/models/event'
|
|
import EventEditorLabel from './editors/EventEditorLabel.vue'
|
|
import EventEditorDescription from './editors/EventEditorDescription.vue'
|
|
import EventEditorDates from './editors/EventEditorDates.vue'
|
|
import EventEditorLocations from './editors/EventEditorLocations.vue'
|
|
import EventEditorParticipants from './editors/EventEditorParticipants.vue'
|
|
import EventEditorNotifications from './editors/EventEditorNotifications.vue'
|
|
import EventEditorOccurrence from './editors/EventEditorOccurrence.vue'
|
|
import EventEditorTags from './editors/EventEditorTags.vue'
|
|
|
|
// Props - wrapped entity with id, data properties
|
|
const props = defineProps<{
|
|
mode: 'edit' | 'view'
|
|
entity: EntityObject | null
|
|
collection?: CollectionObject | null
|
|
calendars?: CollectionObject[]
|
|
}>()
|
|
|
|
// Emits
|
|
const emit = defineEmits<{
|
|
'edit': []
|
|
'cancel': []
|
|
'close': []
|
|
'save': [entity: EntityObject, collection?: CollectionObject | null]
|
|
'delete': [entity: EntityObject, collection?: CollectionObject | null]
|
|
}>()
|
|
|
|
// State
|
|
const loading = ref(false)
|
|
const saving = ref(false)
|
|
const initialEntity = props.entity as EntityObject | null
|
|
const draftEntity = ref<EntityObject | null>(initialEntity?.clone ? initialEntity.clone() : null)
|
|
const selectedCollectionId = ref<string | number>('')
|
|
|
|
// Computed
|
|
const mode = computed(() => props.mode)
|
|
const editableEntity = computed(() => draftEntity.value)
|
|
const entityObject = computed(() => editableEntity.value?.properties as EventObject ?? null)
|
|
const entityFresh = computed(() => !editableEntity.value?.identifier)
|
|
|
|
const calendarOptions = computed(() =>
|
|
(props.calendars || []).map(cal => ({
|
|
title: cal.properties.label || 'Unnamed Calendar',
|
|
value: cal.identifier,
|
|
}))
|
|
)
|
|
|
|
const selectedCalendar = computed(() =>
|
|
(props.calendars || []).find(calendar => calendar.identifier === selectedCollectionId.value) ?? null
|
|
)
|
|
|
|
// Permissions
|
|
const isExternalEvent = computed(() => {
|
|
return false
|
|
})
|
|
|
|
const canEdit = computed(() => {
|
|
return true
|
|
})
|
|
|
|
const canDelete = computed(() => {
|
|
return !isExternalEvent.value
|
|
})
|
|
|
|
// UI helpers
|
|
const entityIcon = computed(() => 'mdi-calendar')
|
|
|
|
// Functions
|
|
const startEdit = () => {
|
|
emit('edit')
|
|
}
|
|
|
|
const cancelEdit = () => {
|
|
const entity = props.entity as EntityObject | null
|
|
draftEntity.value = entity?.clone ? entity.clone() : null
|
|
emit('cancel')
|
|
}
|
|
|
|
const saveEntity = async () => {
|
|
if (!editableEntity.value || !selectedCalendar.value) {
|
|
return
|
|
}
|
|
|
|
saving.value = true
|
|
emit('save', editableEntity.value as EntityObject, selectedCalendar.value)
|
|
saving.value = false
|
|
}
|
|
|
|
// Location management
|
|
const addLocationPhysical = () => {
|
|
entityObject.value?.addLocationPhysical()
|
|
}
|
|
|
|
const removeLocationPhysical = (key: string) => {
|
|
entityObject.value?.removeLocationPhysical(key)
|
|
}
|
|
|
|
const addLocationVirtual = () => {
|
|
entityObject.value?.addLocationVirtual()
|
|
}
|
|
|
|
const removeLocationVirtual = (key: string) => {
|
|
entityObject.value?.removeLocationVirtual(key)
|
|
}
|
|
|
|
// Participant management
|
|
const addParticipant = () => {
|
|
entityObject.value?.addParticipant()
|
|
}
|
|
|
|
const removeParticipant = (key: string) => {
|
|
entityObject.value?.removeParticipant(key)
|
|
}
|
|
|
|
// Notification management
|
|
const addNotification = () => {
|
|
entityObject.value?.addNotification()
|
|
}
|
|
|
|
const removeNotification = (key: string) => {
|
|
entityObject.value?.removeNotification(key)
|
|
}
|
|
|
|
watch(
|
|
() => props.entity,
|
|
(newEntity) => {
|
|
const entity = newEntity as EntityObject | null
|
|
draftEntity.value = entity?.clone ? entity.clone() : null
|
|
selectedCollectionId.value = entity?.collection || props.collection?.identifier || ''
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
watch(
|
|
() => props.mode,
|
|
(newMode) => {
|
|
const entity = props.entity as EntityObject | null
|
|
if (newMode === 'edit' && entity?.clone) {
|
|
draftEntity.value = entity.clone()
|
|
selectedCollectionId.value = entity.collection || props.collection?.identifier || ''
|
|
}
|
|
},
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<v-sheet class="text-start overflow-auto" color="surface" min-height="84vh" max-height="84vh">
|
|
<v-sheet class="event-editor-header d-flex justify-space-between pa-4" color="surface">
|
|
<div>
|
|
<v-icon :icon="entityIcon" class="mr-2" />
|
|
{{ entityObject?.label || 'Nothing Selected' }}
|
|
</div>
|
|
|
|
<div v-if="!loading && editableEntity">
|
|
<v-btn
|
|
v-if="mode === 'view' && canEdit"
|
|
icon="mdi-pencil"
|
|
size="small"
|
|
variant="text"
|
|
@click="startEdit"
|
|
/>
|
|
|
|
<v-btn
|
|
v-if="!entityFresh && canDelete"
|
|
icon="mdi-delete"
|
|
size="small"
|
|
variant="text"
|
|
color="error"
|
|
@click="emit('delete', editableEntity as EntityObject, collection)"
|
|
/>
|
|
|
|
<v-btn
|
|
icon="mdi-close"
|
|
size="small"
|
|
variant="text"
|
|
@click="$emit('close')"
|
|
/>
|
|
</div>
|
|
</v-sheet>
|
|
|
|
<div class="event-editor-fields">
|
|
<v-divider />
|
|
<v-progress-linear v-if="loading" indeterminate color="primary" />
|
|
|
|
<div v-if="!editableEntity" class="text-center pa-8">
|
|
<v-icon icon="mdi-calendar-blank" size="64" color="grey" class="mb-4" />
|
|
<p class="text-h6 text-grey">Select an event to view details</p>
|
|
</div>
|
|
|
|
<div v-else class="pa-4">
|
|
<v-select
|
|
v-if="mode === 'edit' && !editableEntity?.identifier"
|
|
v-model="selectedCollectionId"
|
|
:items="calendarOptions"
|
|
label="Calendar"
|
|
:rules="[value => (value !== '' && value != null) || 'Calendar is required']"
|
|
variant="outlined"
|
|
density="compact"
|
|
class="mb-4"
|
|
/>
|
|
|
|
<EventEditorLabel
|
|
:mode="mode"
|
|
:label="entityObject!.label"
|
|
@update:label="entityObject!.label = $event"
|
|
/>
|
|
|
|
<EventEditorDescription
|
|
:mode="mode"
|
|
:description="entityObject!.description"
|
|
@update:description="entityObject!.description = $event"
|
|
/>
|
|
|
|
<EventEditorTags
|
|
:mode="mode"
|
|
:tags="entityObject!.tags"
|
|
@update:tags="entityObject!.tags = $event"
|
|
/>
|
|
|
|
<EventEditorDates
|
|
:mode="mode"
|
|
:starts-on="entityObject!.startsOn"
|
|
:ends-on="entityObject!.endsOn"
|
|
:timeless="entityObject!.timeless"
|
|
:time-zone="entityObject!.timeZone"
|
|
@update:starts-on="entityObject!.startsOn = $event"
|
|
@update:ends-on="entityObject!.endsOn = $event"
|
|
@update:timeless="entityObject!.timeless = $event"
|
|
@update:time-zone="entityObject!.timeZone = $event"
|
|
/>
|
|
|
|
<EventEditorOccurrence
|
|
:mode="mode"
|
|
:pattern="entityObject!.pattern"
|
|
:starts-on="entityObject!.startsOn"
|
|
@update:pattern="entityObject!.pattern = $event"
|
|
/>
|
|
|
|
<EventEditorLocations
|
|
:mode="mode"
|
|
:locations-physical="entityObject!.locationsPhysical || {}"
|
|
:locations-virtual="entityObject!.locationsVirtual || {}"
|
|
@add-location-physical="addLocationPhysical"
|
|
@remove-location-physical="removeLocationPhysical"
|
|
@add-location-virtual="addLocationVirtual"
|
|
@remove-location-virtual="removeLocationVirtual"
|
|
/>
|
|
|
|
<EventEditorParticipants
|
|
:mode="mode"
|
|
:organizer="entityObject!.organizer"
|
|
:participants="entityObject!.participants || {}"
|
|
@update:organizer="entityObject!.organizer = $event"
|
|
@add-participant="addParticipant"
|
|
@remove-participant="removeParticipant"
|
|
/>
|
|
|
|
<EventEditorNotifications
|
|
:mode="mode"
|
|
:notifications="entityObject!.notifications || {}"
|
|
@add-notification="addNotification"
|
|
@remove-notification="removeNotification"
|
|
/>
|
|
|
|
<!-- Metadata for view mode -->
|
|
<div v-if="mode === 'view' && entityObject!.created" class="event-editor-section">
|
|
<v-divider class="mb-4"></v-divider>
|
|
<div class="text-caption text-medium-emphasis">
|
|
<div>Created: {{ new Date(entityObject!.created).toLocaleString() }}</div>
|
|
<div v-if="entityObject!.modified">Modified: {{ new Date(entityObject!.modified).toLocaleString() }}</div>
|
|
<div v-if="isExternalEvent" class="mt-2">
|
|
<v-chip size="small" color="info" variant="tonal">
|
|
<v-icon start size="small">mdi-account-arrow-right</v-icon>
|
|
External Event
|
|
</v-chip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<v-divider/>
|
|
</div>
|
|
|
|
<v-sheet class="event-editor-footer d-flex text-end justify-space-between pa-4" color="surface">
|
|
<v-btn v-if="mode === 'edit'"
|
|
variant="text"
|
|
@click="cancelEdit"
|
|
:disabled="saving">
|
|
Cancel
|
|
</v-btn>
|
|
|
|
<v-btn v-if="mode === 'edit'"
|
|
color="primary"
|
|
variant="elevated"
|
|
@click="saveEntity"
|
|
:disabled="!selectedCalendar"
|
|
:loading="saving">
|
|
Save
|
|
</v-btn>
|
|
</v-sheet>
|
|
</v-sheet>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.event-editor-section {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.event-editor-header,
|
|
.event-editor-footer {
|
|
position: sticky;
|
|
z-index: 2;
|
|
}
|
|
|
|
.event-editor-header {
|
|
top: 0;
|
|
}
|
|
|
|
.event-editor-footer {
|
|
bottom: 0;
|
|
}
|
|
</style>
|