1. add chat card 2. fix settings (output object) 3. add input phone and email to user 4. fix empty string to null
317 lines
8.9 KiB
Vue
317 lines
8.9 KiB
Vue
<template>
|
|
<div class="q-pa-none flex column col-grow no-scroll">
|
|
<pn-scroll-list>
|
|
<template #card-body-header>
|
|
|
|
<div class="flex row q-ma-md justify-between" v-if="chats.length !== 0">
|
|
<q-input
|
|
v-model="search"
|
|
clearable
|
|
clear-icon="close"
|
|
:placeholder="$t('chats__search')"
|
|
dense
|
|
class="col-grow"
|
|
v-if="chats.length !== 0"
|
|
>
|
|
<template #prepend>
|
|
<q-icon name="mdi-magnify" />
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
</template>
|
|
|
|
<q-list separator v-if="chats.length !== 0">
|
|
<q-slide-item
|
|
v-for="item in displayChats"
|
|
:key="item.id"
|
|
@right="handleSlide($event, item.id)"
|
|
right-color="red"
|
|
@click="goChatInfo(item.id)"
|
|
>
|
|
<template #right>
|
|
<q-icon size="lg" name="mdi-link-off"/>
|
|
</template>
|
|
|
|
<q-item
|
|
clickable
|
|
v-ripple
|
|
>
|
|
<q-item-section avatar>
|
|
<pn-auto-avatar
|
|
:img="item.logo"
|
|
:name="item.name"
|
|
type="rounded"
|
|
size="lg"
|
|
/>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label lines="1" class="text-bold">
|
|
{{ item.name }}
|
|
</q-item-label>
|
|
<q-item-label caption lines="2">
|
|
{{ item.description }}
|
|
</q-item-label>
|
|
<q-item-label caption lines="1">
|
|
<div class = "flex justify-start items-center no-wrap">
|
|
<div class="q-mr-sm flex items-center no-wrap">
|
|
<q-icon name="mdi-account-multiple-outline" class="q-mr-xs"/>
|
|
<span>{{ item.user_count }}</span>
|
|
</div>
|
|
<div class="q-mx-sm flex items-center no-wrap ellipsis" v-if="item.owner_id">
|
|
<q-icon name="mdi-key" class="q-mr-xs"/>
|
|
<span class="ellipsis">{{ usersStore.userNameById(item.owner_id) }} </span>
|
|
</div>
|
|
</div>
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-slide-item>
|
|
</q-list>
|
|
|
|
<pn-onboard-btn
|
|
v-if="chats.length === 0 && chatsInit"
|
|
icon="mdi-chat-plus-outline"
|
|
:message1="$t('chats__onboard_msg1')"
|
|
:message2="$t('chats__onboard_msg2')"
|
|
@click="showOverlay=true; fabState=true"
|
|
/>
|
|
<div
|
|
class="flex column justify-center items-center w100"
|
|
style="position: absolute; bottom: 0;"
|
|
v-if="!chatsInit"
|
|
>
|
|
<q-linear-progress indeterminate />
|
|
</div>
|
|
</pn-scroll-list>
|
|
|
|
<q-page-sticky
|
|
:style="{ zIndex: !showOverlay ? 'inherit' : '5100 !important' }"
|
|
position="bottom-right"
|
|
:offset="[0, 18]"
|
|
class="fix-fab-offset"
|
|
>
|
|
<transition
|
|
appear
|
|
enter-active-class="animated zoomIn"
|
|
>
|
|
<q-fab
|
|
v-model="fabState"
|
|
v-if="showFab"
|
|
icon="add"
|
|
color="brand"
|
|
direction="up"
|
|
vertical-actions-align="right"
|
|
@click="showOverlay = !showOverlay"
|
|
:disable="!tg.initData"
|
|
>
|
|
<template #tooltip>
|
|
<q-tooltip
|
|
v-if="!tg.initData"
|
|
anchor="center left" self="center end"
|
|
style="width: calc(min(100vw, var(--body-width)) - 102px) !important;"
|
|
>
|
|
{{ $t('chats_disabled_FAB')}}
|
|
</q-tooltip>
|
|
</template>
|
|
<q-fab-action
|
|
v-for="item in fabMenu"
|
|
:key="item.id"
|
|
square
|
|
clickable
|
|
v-ripple
|
|
class="bg-white change-fab-action"
|
|
@click="item.func()"
|
|
>
|
|
|
|
<template #icon>
|
|
<q-item class="q-pa-xs w100">
|
|
<q-item-section avatar class="items-center">
|
|
<q-avatar color="brand" rounded text-color="white" :icon="item.icon" />
|
|
</q-item-section>
|
|
|
|
<q-item-section class="items-start">
|
|
<q-item-label class="fab-action-item">
|
|
{{ $t(item.name) }}
|
|
</q-item-label>
|
|
<q-item-label caption class="fab-action-item">
|
|
{{ $t(item.description) }}
|
|
</q-item-label>
|
|
|
|
</q-item-section>
|
|
</q-item>
|
|
</template>
|
|
</q-fab-action>
|
|
</q-fab>
|
|
</transition>
|
|
</q-page-sticky>
|
|
</div>
|
|
|
|
<pn-overlay v-if="showOverlay"/>
|
|
|
|
<pn-small-dialog
|
|
v-model="showDialogDeleteChat"
|
|
icon="mdi-link-off"
|
|
color="negative"
|
|
title="chats__dialog_unlink_title"
|
|
message1="chats__dialog_unlink_message"
|
|
message2="chats__dialog_unlink_message2"
|
|
mainBtnLabel="chats__dialog_unlink_ok"
|
|
@clickMainBtn="onConfirm()"
|
|
@close="onCancel()"
|
|
@before-hide="onDialogBeforeHide()"
|
|
/>
|
|
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onActivated, onDeactivated, onBeforeUnmount, inject } from 'vue'
|
|
import { useChatsStore } from 'stores/chats'
|
|
import { useUsersStore } from 'stores/users'
|
|
import type { WebApp } from '@twa-dev/types'
|
|
import { useI18n } from "vue-i18n"
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
|
|
const { t } = useI18n()
|
|
|
|
const search = ref('')
|
|
const showOverlay = ref<boolean>(false)
|
|
const chatsStore = useChatsStore()
|
|
const usersStore = useUsersStore()
|
|
const showDialogDeleteChat = ref<boolean>(false)
|
|
const deleteChatId = ref<number | undefined>(undefined)
|
|
const currentSlideEvent = ref<SlideEvent | null>(null)
|
|
const closedByUserAction = ref(false)
|
|
const tg = inject('tg') as WebApp
|
|
|
|
const fabState = ref(false)
|
|
|
|
interface SlideEvent {
|
|
reset: () => void
|
|
}
|
|
|
|
const chats = chatsStore.getChats
|
|
const chatsInit = computed(() => chatsStore.isInit)
|
|
|
|
const fabMenu = [
|
|
{id: 1, icon: 'mdi-chat-plus-outline', name: 'chats__attach_chat', description: 'chats__attach_chat_description', func: attachChat},
|
|
{id: 2, icon: 'mdi-share-outline', name: 'chats__send_chat', description: 'chats__send_chat_description', func: sendChat},
|
|
]
|
|
|
|
const displayChats = computed(() => {
|
|
if (!search.value || !(search.value && search.value.trim())) return chats
|
|
const searchValue = search.value.trim().toLowerCase()
|
|
const arrOut = chats
|
|
.filter(el =>
|
|
el.name.toLowerCase().includes(searchValue) ||
|
|
el.description && el.description.toLowerCase().includes(searchValue)
|
|
)
|
|
return arrOut
|
|
})
|
|
|
|
function handleSlide (event: SlideEvent, id: number) {
|
|
currentSlideEvent.value = event
|
|
showDialogDeleteChat.value = true
|
|
deleteChatId.value = id
|
|
}
|
|
|
|
function onDialogBeforeHide () {
|
|
if (!closedByUserAction.value) {
|
|
onCancel()
|
|
}
|
|
closedByUserAction.value = false
|
|
}
|
|
|
|
function onCancel() {
|
|
closedByUserAction.value = true
|
|
if (currentSlideEvent.value) {
|
|
currentSlideEvent.value.reset()
|
|
currentSlideEvent.value = null
|
|
}
|
|
}
|
|
|
|
async function onConfirm() {
|
|
closedByUserAction.value = true
|
|
if (deleteChatId.value) {
|
|
await chatsStore.unlink(deleteChatId.value)
|
|
}
|
|
currentSlideEvent.value = null
|
|
}
|
|
|
|
const botName = 'ready_or_not_2025_bot'
|
|
const urlAdmin = 'https://t.me/' + botName + '?startgroup='
|
|
const urlAdminPermission='&admin=' +
|
|
'post_messages+' +
|
|
'edit_messages+' +
|
|
'delete_messages+' +
|
|
'pin_messages+' +
|
|
'restrict_members+' +
|
|
'invite_users'
|
|
|
|
async function attachChat () {
|
|
const key = await chatsStore.getKey()
|
|
tg.openTelegramLink(urlAdmin + key + urlAdminPermission)
|
|
}
|
|
|
|
async function sendChat () {
|
|
const key = await chatsStore.getKey()
|
|
const message = urlAdmin + key + urlAdminPermission
|
|
const tgShareUrl = 'https://t.me/share/url?url=' +
|
|
encodeURIComponent( t('chats__send_chat_title')) +
|
|
'&text=' + `${encodeURIComponent(message)}`
|
|
tg.openTelegramLink(tgShareUrl)
|
|
}
|
|
|
|
async function goChatInfo (chatId: number) {
|
|
await router.push({ name: 'chat_info', params: { id: route.params.id, chatId }})
|
|
}
|
|
|
|
// fix fab jumping
|
|
const showFab = ref(false)
|
|
const timerId = ref<ReturnType<typeof setTimeout> | null>(null)
|
|
|
|
onActivated(() => {
|
|
timerId.value = setTimeout(() => {
|
|
showFab.value = true
|
|
}, 300)
|
|
})
|
|
|
|
onDeactivated(() => {
|
|
showFab.value = false
|
|
if (timerId.value) {
|
|
clearTimeout(timerId.value)
|
|
timerId.value = null
|
|
}
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
if (timerId.value) clearTimeout(timerId.value)
|
|
})
|
|
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* width of choose element */
|
|
.change-fab-action .q-fab__label--internal {
|
|
max-height: none;
|
|
}
|
|
|
|
.change-fab-action {
|
|
width: calc(min(100vw, var(--body-width)) - 48px) !important;
|
|
}
|
|
|
|
.fab-action-item {
|
|
text-wrap: auto !important;
|
|
text-align: left;
|
|
}
|
|
|
|
/* fix mini border after slide */
|
|
:deep(.q-slide-item__right)
|
|
{
|
|
align-self: center;
|
|
height: 98%;
|
|
}
|
|
</style>
|