319 lines
9.7 KiB
Vue
319 lines
9.7 KiB
Vue
<template>
|
|
<pn-page-card>
|
|
<template #title>
|
|
{{ $t(title) }}
|
|
</template>
|
|
<template #footer>
|
|
<q-btn
|
|
rounded color="primary"
|
|
class="w100 q-mt-md q-mb-xs fix-disabled-btn"
|
|
:disable="!(isFormValid && (isDirty(initialMeeting, modelValue) || newFiles.length !== 0))"
|
|
@click = "emit('update', newFiles)"
|
|
>
|
|
{{ $t(btnText) }}
|
|
</q-btn>
|
|
</template>
|
|
|
|
<pn-scroll-list>
|
|
<div class="flex column items-center q-pa-md q-pb-sm">
|
|
<div class="q-gutter-y-lg w100">
|
|
<q-select
|
|
v-model="modelValue.chat_id"
|
|
:options="displayChats"
|
|
dense
|
|
filled
|
|
class="w100"
|
|
:label = "$t('meeting_block__attach_chat')"
|
|
option-value="id"
|
|
option-label="name"
|
|
emit-value
|
|
map-options
|
|
>
|
|
<template #prepend>
|
|
<q-icon name="mdi-chat-outline"/>
|
|
</template>
|
|
<template #option="scope">
|
|
<q-item v-bind="scope.itemProps">
|
|
<q-item-section avatar>
|
|
<q-avatar rounded>
|
|
<template v-if="scope.opt.id">
|
|
<pn-auto-avatar
|
|
:img="scope.opt.logo"
|
|
:name="scope.opt.name"
|
|
size="md"
|
|
/>
|
|
</template>
|
|
<template v-else>
|
|
<q-icon size="32px" color="grey" name="mdi-cancel"/>
|
|
</template>
|
|
</q-avatar>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>{{ scope.opt.name }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</template>
|
|
</q-select>
|
|
|
|
<q-input
|
|
v-model.trim="modelValue.name"
|
|
dense
|
|
filled
|
|
class = "w100 fix-bottom-padding q-pt-sm"
|
|
:rules="[rules.name]"
|
|
no-error-icon
|
|
label-slot
|
|
>
|
|
<template #label>
|
|
{{$t('meeting_block__name') }}
|
|
<span class="text-red">*</span>
|
|
</template>
|
|
</q-input>
|
|
|
|
<q-input
|
|
v-model="modelValue.description"
|
|
dense
|
|
filled
|
|
autogrow
|
|
class="w100 q-pt-sm"
|
|
:label="$t('meeting_block__description')"
|
|
/>
|
|
|
|
<div class="flex no-wrap justify-between q-gutter-x-md q-pt-sm">
|
|
<q-input
|
|
v-model="meetingDate"
|
|
dense filled
|
|
mask="##/##/####"
|
|
:rules=[rules.date]
|
|
no-error-icon
|
|
:label="$t('meeting_block__date')"
|
|
hide-bottom-space
|
|
>
|
|
<template #prepend>
|
|
<q-icon name="event" class="cursor-pointer">
|
|
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
|
<q-date
|
|
v-model="meetingDate"
|
|
mask="DD/MM/YYYY"
|
|
class="relative-position"
|
|
:options="d => d >= date.formatDate(Date.now(), 'YYYY/MM/DD')"
|
|
:navigation-min-year-month="date.formatDate(Date.now(), 'YYYY/MM')"
|
|
>
|
|
<div class="absolute" style="top: 0; right: 0;">
|
|
<q-btn
|
|
v-close-popup
|
|
round flat
|
|
color="white"
|
|
icon="mdi-close"
|
|
class="q-ma-sm"
|
|
/>
|
|
</div>
|
|
</q-date>
|
|
</q-popup-proxy>
|
|
</q-icon>
|
|
</template>
|
|
</q-input>
|
|
|
|
<q-input
|
|
v-model="meetingTime"
|
|
dense filled
|
|
mask="time"
|
|
:rules="[rules.time]"
|
|
no-error-icon
|
|
:label="$t('meeting_block__time')"
|
|
hide-bottom-space
|
|
>
|
|
<template #prepend>
|
|
<q-icon name="access_time" class="cursor-pointer">
|
|
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
|
<q-time
|
|
v-model="meetingTime"
|
|
mask="HH:mm"
|
|
format24h
|
|
class="relative-position"
|
|
>
|
|
<div class="absolute" style="top: 0; right: 0;">
|
|
<q-btn
|
|
v-close-popup
|
|
round flat
|
|
color="white"
|
|
icon="mdi-close"
|
|
class="q-ma-sm"
|
|
/>
|
|
</div>
|
|
</q-time>
|
|
</q-popup-proxy>
|
|
</q-icon>
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
|
|
<q-input
|
|
v-model.trim="modelValue.place"
|
|
dense
|
|
filled
|
|
class = "w100 q-pt-sm"
|
|
no-error-icon
|
|
:label="$t('meeting_block__place')"
|
|
>
|
|
<template #prepend>
|
|
<q-icon name="mdi-map-marker-outline"/>
|
|
</template>
|
|
</q-input>
|
|
|
|
<q-select
|
|
v-model="modelValue.participants"
|
|
:options="displayUsers"
|
|
dense
|
|
filled
|
|
class="w100 file-input-fix q-pt-sm"
|
|
:label = "$t('meeting_block__participants')"
|
|
option-value="id"
|
|
option-label="displayName"
|
|
emit-value
|
|
map-options
|
|
use-chips
|
|
multiple
|
|
>
|
|
<template #prepend>
|
|
<q-icon name="mdi-account-outline"/>
|
|
</template>
|
|
<template #option="scope">
|
|
<q-item v-bind="scope.itemProps">
|
|
<q-item-section avatar>
|
|
<pn-auto-avatar
|
|
:img="scope.opt.photo"
|
|
:name="scope.opt.displayName"
|
|
size="md"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>{{ scope.opt.displayName }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</template>
|
|
</q-select>
|
|
<pn-file-uploader
|
|
v-model:exist-files ="modelValue.files"
|
|
v-model:new-files ="newFiles"
|
|
:existFileData="files"
|
|
:label="$t('meeting_block__attach_files')"
|
|
class="q-pt-sm"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</pn-scroll-list>
|
|
</pn-page-card>
|
|
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { onMounted, computed, ref } from 'vue'
|
|
import type { MeetingParams } from 'types/Meeting'
|
|
import { useChatsStore } from 'stores/chats'
|
|
import { useUsersStore } from 'stores/users'
|
|
import { useFilesStore } from 'stores/files'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { isDirty } from 'helpers/helpers'
|
|
import { date } from 'quasar'
|
|
const { t } = useI18n()
|
|
const filesStore = useFilesStore()
|
|
const files = filesStore.getFiles
|
|
const newFiles=ref<File[]>([])
|
|
|
|
const modelValue = defineModel<MeetingParams>({
|
|
required: true
|
|
})
|
|
|
|
defineProps<{
|
|
title: string,
|
|
btnText: string
|
|
}>()
|
|
|
|
const emit = defineEmits(['update'])
|
|
|
|
const chatsStore = useChatsStore()
|
|
const chats = chatsStore.getChats
|
|
const displayChats = computed(() => [
|
|
...chats.map(el => ({
|
|
id: el.id,
|
|
name: el.name,
|
|
logo: el.logo
|
|
})),
|
|
{
|
|
id: null,
|
|
name: t('meeting_block__no_chat'),
|
|
logo: ''
|
|
}
|
|
])
|
|
|
|
const usersStore = useUsersStore()
|
|
const users = usersStore.getUsers
|
|
|
|
const displayUsers = computed(() => {
|
|
return users
|
|
.map(el => ({ ...el, displayName: usersStore.userNameById(el.id) }))
|
|
})
|
|
|
|
const meetingDate = computed({
|
|
get: () => date.formatDate(modelValue.value.meet_date * 1000, 'DD/MM/YYYY'),
|
|
set: (d) => updateDateTime(d, meetingTime.value)
|
|
})
|
|
|
|
const meetingTime = computed({
|
|
get: () => date.formatDate(modelValue.value.meet_date * 1000, 'HH:mm'),
|
|
set: (t) => updateDateTime(meetingDate.value, t)
|
|
})
|
|
|
|
function updateDateTime (dateStr: string, timeStr: string) {
|
|
if (dateStr.length === 10 && timeStr.length === 5) {
|
|
const newDate = date.extractDate(`${dateStr} ${timeStr}`, 'DD/MM/YYYY HH:mm')
|
|
if (!isNaN(newDate.getTime())) {
|
|
modelValue.value.meet_date = newDate.getTime() / 1000
|
|
}
|
|
}
|
|
}
|
|
|
|
const rulesErrorMessage = {
|
|
name: t('meeting_block__error_name'),
|
|
date: t('meeting_block__error_date'),
|
|
time: t('meeting_block__error_time')
|
|
}
|
|
|
|
const rules = {
|
|
name: (val: MeetingParams['name']) => !!val?.trim() || rulesErrorMessage['name'],
|
|
date: () => (!!modelValue.value.meet_date && modelValue.value.meet_date > Date.now() / 1000) || rulesErrorMessage['date'],
|
|
time: () => (!!modelValue.value.meet_date && modelValue.value.meet_date > Date.now() / 1000) || rulesErrorMessage['time']
|
|
}
|
|
|
|
const isFormValid = computed(() => {
|
|
const validations = {
|
|
name: rules.name(modelValue.value.name) === true,
|
|
date: rules.date() === true,
|
|
time: rules.time() === true
|
|
}
|
|
|
|
return Object.values(validations).every(Boolean)
|
|
})
|
|
|
|
const initialMeeting = ref({} as MeetingParams)
|
|
|
|
onMounted(() => {
|
|
initialMeeting.value = { ...modelValue.value }
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fix-bottom-padding.q-field--with-bottom {
|
|
padding-bottom: 0 !important
|
|
}
|
|
|
|
.file-input-fix :deep(.q-field__append) {
|
|
height: auto !important;
|
|
}
|
|
|
|
.file-input-fix :deep(.q-field__prepend) {
|
|
height: auto !important;
|
|
}
|
|
</style>
|