before store

This commit is contained in:
2025-06-26 11:06:48 +03:00
parent 1c732e16dd
commit 34baeb40e3
59 changed files with 3180 additions and 2149 deletions

View File

@@ -1,253 +1,307 @@
<template>
<div class="flex column items-center q-pa-lg">
<div class="q-gutter-y-lg w100">
<q-input
v-model.trim="modelValue.name"
dense
filled
class = "w100 fix-bottom-padding"
:rules="[rules.name]"
no-error-icon
label-slot
<pn-page-card>
<template #title>
{{ $t(title) }}
</template>
<template #footer>
<q-btn
rounded color="primary"
class="w100 q-mt-md q-mb-xs"
:disable="!(isFormValid && (isDirty(initialMeeting, modelValue) || newFiles.length !== 0))"
@click = "emit('update', newFiles)"
>
<template #label>
{{$t('meeting_info__name') }}
<span class="text-red">*</span>
</template>
</q-input>
{{ $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="modelValue.description"
dense
filled
autogrow
class="w100 q-pt-sm"
:label="$t('meeting_info__description')"
/>
<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>
<div class="flex no-wrap justify-between q-gutter-x-md q-pt-sm">
<q-input
v-model="meetingDate"
dense filled
mask="##/##/####"
:label="$t('meeting_info__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="modelValue.description"
dense
filled
autogrow
class="w100 q-pt-sm"
:label="$t('meeting_block__description')"
/>
<q-input
v-model="meetingTime"
dense filled
mask="time"
:rules="['time']"
:label="$t('meeting_info__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 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>
<q-select
v-model="modelValue.chat_attach"
:options="chats"
dense
filled
class="w100 q-pt-sm"
:label = "$t('meeting_info__attach_chat')"
option-value="id"
option-label="name"
emit-value
map-options
label-slot
:disable="chats.length<=1"
:placeholder="chats.length<=1 ? undefined : t('meeting_info__choose_chat_placeholder')"
>
<template #prepend>
<q-icon name="mdi-chat-outline"/>
</template>
<template #label>
{{$t('meeting_info__attach_chat') }}
<span class="text-red" v-if="chats.length>1">*</span>
</template>
<template #option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>
<q-avatar rounded size="md">
<img v-if="scope.opt.logo" :src="scope.opt.logo"/>
<pn-auto-avatar v-else :name="scope.opt.name"/>
</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-select
v-model="modelValue.participants"
:options="displayUsers"
dense
filled
class="w100 file-input-fix q-pt-sm"
:label = "$t('meeting_info__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>
<q-avatar round size="md">
<img v-if="scope.opt.photo" :src="scope.opt.photo"/>
<pn-auto-avatar v-else :name="scope.opt.name"/>
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label>{{ scope.opt.displayName }}</q-item-label>
</q-item-section>
</q-item>
</template>
</q-select>
<q-file
v-model="modelValue.files"
:label="$t('meeting_info__attach_files')"
outlined
use-chips
multiple
dense
class="file-input-fix q-pt-sm"
>
<template #prepend>
<q-icon name="attach_file"/>
</template>
</q-file>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, watch, computed } from 'vue'
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 { t } = useI18n()
const filesStore = useFilesStore()
const files = filesStore.getFiles
const newFiles=ref<File[]>([])
const modelValue = defineModel<MeetingParams>({
required: true
})
const emit = defineEmits(['valid'])
const rulesErrorMessage = {
name: t('meeting_info__error_name'),
dateMeeting: t('meeting_info__error_date'),
timeMeeting: t('meeting_info__error_time')
}
defineProps<{
title: string,
btnText: string
}>()
const emit = defineEmits(['update'])
const chatsStore = useChatsStore()
const chats = computed(() => chatsStore.chats)
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 = computed(() => usersStore.users)
const users = usersStore.getUsers
const displayUsers = computed(() => {
return users.value
return users
.map(el => ({ ...el, displayName: usersStore.userNameById(el.id) }))
})
const meetingDate = computed({
get: () => date.formatDate(modelValue.value.meet_date, 'DD/MM/YYYY'),
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, 'HH:mm'),
get: () => date.formatDate(modelValue.value.meet_date * 1000, 'HH:mm'),
set: (t) => updateDateTime(meetingDate.value, t)
})
function updateDateTime(dateStr: string, timeStr: string) {
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()
modelValue.value.meet_date = newDate.getTime() / 1000
}
}
}
const rules = {
name: (val: MeetingParams['name']) => !!val?.trim() || rulesErrorMessage['name']
const rulesErrorMessage = {
name: t('meeting_block__error_name'),
date: t('meeting_block__error_date'),
time: t('meeting_block__error_time')
}
const isValid = computed(() => {
const checkName = rules.name(modelValue.value.name)
return { name: checkName && (checkName !== rulesErrorMessage['name']) }
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)
})
watch(isValid, (newVal) => {
const allValid = Object.values(newVal).every(v => v)
emit('valid', allValid)
}, { immediate: true})
const initialMeeting = ref({} as MeetingParams)
onMounted(() => {
if (chats.value.length === 1 && !modelValue.value.chat_attach) {
modelValue.value.chat_attach = chats.value[0]?.id ?? null
}
initialMeeting.value = { ...modelValue.value }
})
</script>
<style scoped>