add chat-card
1. add chat card 2. fix settings (output object) 3. add input phone and email to user 4. fix empty string to null
This commit is contained in:
BIN
i18n-2.xlsm
BIN
i18n-2.xlsm
Binary file not shown.
@@ -26,18 +26,14 @@ const api = axios.create({
|
||||
api.interceptors.response.use(
|
||||
response => response,
|
||||
async (error: AxiosError<{ error?: { code: string; message: string } }>) => {
|
||||
console.log(error)
|
||||
const errorData = error.response?.data?.error || {
|
||||
code: 'ZERO',
|
||||
message: error.message || 'Unknown error'
|
||||
}
|
||||
|
||||
const serverError = new ServerError(
|
||||
errorData.code,
|
||||
errorData.message
|
||||
)
|
||||
const serverError = new ServerError(errorData.code, errorData.message)
|
||||
|
||||
if (!error.config?.suppressNotify) {
|
||||
if (error.config && !(error.config as AxiosRequestConfig).suppressNotify) {
|
||||
Notify.create({
|
||||
type: 'negative',
|
||||
message: errorData.code + ': ' + errorData.message,
|
||||
|
||||
@@ -98,6 +98,28 @@
|
||||
class = "w100 q-pt-sm"
|
||||
:label = "$t('user_block__role')"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
v-model.trim="modelValue.phone"
|
||||
dense
|
||||
filled
|
||||
class = "w100 q-pt-sm"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon name="mdi-phone-outline"/>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-input
|
||||
v-model.trim="modelValue.email"
|
||||
dense
|
||||
filled
|
||||
class = "w100 q-pt-sm"
|
||||
>
|
||||
<template #prepend>
|
||||
<q-icon name="mdi-email-outline"/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
37
src/composables/useUserSection.ts
Normal file
37
src/composables/useUserSection.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useCompaniesStore } from 'stores/companies'
|
||||
import type { User } from 'types/Users'
|
||||
|
||||
export function useUserSection() {
|
||||
const companiesStore = useCompaniesStore()
|
||||
|
||||
const userSection = (user: User) => {
|
||||
const tname = () => {
|
||||
return user.firstname
|
||||
? user.lastname
|
||||
? user.firstname + ' ' + user.lastname
|
||||
: user.firstname
|
||||
: user.lastname ?? ''
|
||||
};
|
||||
|
||||
const section1 = user.fullname ?? tname()
|
||||
const section2_1 = user.fullname ? tname() : ''
|
||||
const section2_2 = user.username ?? ''
|
||||
|
||||
const section3 = (
|
||||
user.company_id && companiesStore.companyById(user.company_id)
|
||||
? companiesStore.companyById(user.company_id)?.name + ((user.role || user.department) ? ' / ' : '')
|
||||
: ''
|
||||
) +
|
||||
(user.department ? user.department + (user.role ? ' / ' : '') : '') +
|
||||
(user.role ?? '')
|
||||
|
||||
return {
|
||||
section1,
|
||||
section2_1,
|
||||
section2_2,
|
||||
section3
|
||||
}
|
||||
}
|
||||
|
||||
return { userSection }
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,91 +1,142 @@
|
||||
<template>
|
||||
<pn-page-card>
|
||||
<template #title>
|
||||
<pn-account-block-name/>
|
||||
{{ $t('chat_card__title') }}
|
||||
</template>
|
||||
<template #footer>
|
||||
<q-btn
|
||||
@click="logout()"
|
||||
flat
|
||||
round
|
||||
icon="mdi-logout"
|
||||
/>
|
||||
rounded color="primary"
|
||||
class="w100 q-mt-md q-mb-xs"
|
||||
@click = "onSubmit"
|
||||
v-if="chat && chat.invite_link"
|
||||
>
|
||||
{{ $t("chat_card__go_chat") }}
|
||||
</q-btn>
|
||||
</template>
|
||||
|
||||
<pn-scroll-list>
|
||||
<q-list separator>
|
||||
<q-item
|
||||
v-for="item in displayItems"
|
||||
:key="item.id"
|
||||
@click="goTo(item.pathName)"
|
||||
clickable
|
||||
v-ripple
|
||||
<div
|
||||
v-if="chat"
|
||||
class="flex column items-center q-pa-md q-pb-lg"
|
||||
>
|
||||
<pn-auto-avatar
|
||||
:img="chat.logo"
|
||||
:name="chat.name"
|
||||
size="100px"
|
||||
rounded
|
||||
/>
|
||||
<div
|
||||
v-if="chat.description"
|
||||
class="flex row items-start justify-center q-pt-md"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-avatar
|
||||
:icon="item.icon"
|
||||
:color="item.iconColor ? item.iconColor: 'brand'"
|
||||
text-color="white"
|
||||
rounded
|
||||
font-size ="26px"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
{{ $t(item.name) }}
|
||||
</q-item-label>
|
||||
<q-item-label class="text-caption" v-if="$te(item.description)">
|
||||
{{ $t(item.description) }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
{{ chat.description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-list separator v-if="chatUsers.length!==0">
|
||||
<q-item-label header>
|
||||
{{ $t('chat_page__members') + ' (' + chatUsers.length +')' }}
|
||||
</q-item-label>
|
||||
<q-item
|
||||
v-for="item in chatUsers"
|
||||
:key="item.id"
|
||||
v-ripple
|
||||
clickable
|
||||
class="w100"
|
||||
@click="goUserInfo(item.id)"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<pn-auto-avatar
|
||||
:img="item.photo"
|
||||
:name="item.section1"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label lines="1" class="text-bold" v-if="item.section1">
|
||||
<q-badge
|
||||
v-if="item.is_blocked"
|
||||
color="negative"
|
||||
>
|
||||
{{ $t('chat_page__user_blocked') }}
|
||||
</q-badge>
|
||||
{{item.section1}}
|
||||
</q-item-label>
|
||||
<q-item-label lines="1" caption v-if="item.section3">
|
||||
{{item.section3}}
|
||||
</q-item-label>
|
||||
<q-item-label caption lines="2">
|
||||
<div class="flex items-center">
|
||||
<q-icon name="telegram" v-if="item.section2_1 || item.section2_2" class="q-pr-xs" style="color: #27a7e7"/>
|
||||
<div v-if="item.section2_1" class="q-mr-sm text-bold">{{item.section2_1}}</div>
|
||||
<div class="text-blue" v-if="item.section2_2">{{'@' + item.section2_2}}</div>
|
||||
</div>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</pn-scroll-list>
|
||||
</pn-page-card>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from 'stores/auth'
|
||||
import { ref, computed, watch, inject } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useChatsStore } from 'stores/chats'
|
||||
import { useUsersStore } from 'stores/users'
|
||||
import { useCompaniesStore } from 'stores/companies'
|
||||
import { parseIntString } from 'helpers/helpers'
|
||||
import type { Chat } from 'types/Chat'
|
||||
import type { WebApp } from '@twa-dev/types'
|
||||
import { useUserSection } from 'composables/useUserSection'
|
||||
|
||||
const tg = inject('tg') as WebApp
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
const route = useRoute()
|
||||
const chatsStore = useChatsStore()
|
||||
const usersStore = useUsersStore()
|
||||
|
||||
interface ItemList {
|
||||
id: number
|
||||
name: string
|
||||
description?: string
|
||||
icon: string
|
||||
iconColor?: string
|
||||
pathName: string
|
||||
display?: boolean
|
||||
const chat = ref<Chat | null>(null)
|
||||
const chatId = computed(() => parseIntString(route.params.chatId))
|
||||
|
||||
function initChat() {
|
||||
if (chatsStore.isInit && chatId.value) {
|
||||
const foundChat = chatsStore.chatById(chatId.value)
|
||||
if (foundChat) {
|
||||
chat.value = { ...foundChat } as Chat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const items = computed(() => ([
|
||||
{ id: 1, name: 'account__subscribe', description: 'account__subscribe_description', icon: 'mdi-crown-circle-outline', iconColor: 'orange', pathName: 'subscribe' },
|
||||
{ id: 2, name: 'account__auth_change_method', description: 'account__auth_change_method_description', icon: 'mdi-account-sync-outline', iconColor: 'primary', pathName: 'change_account_auth_method', display: !authStore.customer?.email },
|
||||
{ id: 3, name: 'account__auth_change_password', description: 'account__auth_change_password_description', icon: 'mdi-account-key-outline', iconColor: 'primary', pathName: 'change_account_password', display: !!authStore.customer?.email },
|
||||
{ id: 4, name: 'account__auth_change_account', description: 'account__auth_change_account_description', icon: 'mdi-account-switch-outline', iconColor: 'primary', pathName: 'change_account_email', display: !!authStore.customer?.email },
|
||||
{ id: 5, name: 'account__company_data', icon: 'mdi-account-group-outline', description: 'account__company_data_description', pathName: 'your_company' },
|
||||
{ id: 6, name: 'account__settings', icon: 'mdi-cog-outline', description: 'account__settings_description', iconColor: 'info', pathName: 'settings' },
|
||||
{ id: 7, name: 'account__support', icon: 'mdi-lifebuoy', description: 'account__support_description', iconColor: 'info', pathName: 'support' },
|
||||
{ id: 9, name: 'account__terms_of_use', icon: 'mdi-book-open-variant-outline', description: '', iconColor: 'grey', pathName: 'terms' },
|
||||
{ id: 10, name: 'account__privacy', icon: 'mdi-lock-outline', description: '', iconColor: 'grey', pathName: 'privacy' }
|
||||
]))
|
||||
if (chatsStore.isInit) initChat()
|
||||
|
||||
const displayItems = computed(() => (
|
||||
items.value.filter((item: ItemList) => !('display' in item) || item.display === true)
|
||||
))
|
||||
watch(() => chatsStore.isInit, initChat)
|
||||
|
||||
async function goTo (path: string) {
|
||||
await router.push({ name: path })
|
||||
function onSubmit () {
|
||||
if (chat.value && chat.value.invite_link) tg.openTelegramLink(chat.value.invite_link)
|
||||
if (chat && chat.value) chatsStore.getChatUsers(chat.value.id)
|
||||
}
|
||||
|
||||
async function logout () {
|
||||
await authStore.logout()
|
||||
await router.push({ name: 'login' })
|
||||
const users = usersStore.getUsers
|
||||
const companiesStore = useCompaniesStore()
|
||||
const { userSection } = useUserSection()
|
||||
|
||||
const chatUsers = computed(() => {
|
||||
if (!chat || !chat.value) return []
|
||||
const idSet = new Set(chat.value.chat_users)
|
||||
const arr = users.filter(el => idSet.has(el.id))
|
||||
return arr.map(el => ({
|
||||
...el,
|
||||
...userSection(el),
|
||||
companyName: el.company_id && companiesStore.companyById(el.company_id)
|
||||
? companiesStore.companyById(el.company_id)?.name
|
||||
: null
|
||||
}))
|
||||
})
|
||||
|
||||
async function goUserInfo (id: number) {
|
||||
await router.push({ name: 'user_info', params: { id: route.params.id, userId: id }})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
caption="settings__bot_title"
|
||||
icon="mdi-map-clock-outline"
|
||||
iconColor="primary"
|
||||
v-if="timeZoneBot"
|
||||
>
|
||||
<template #value>
|
||||
{{ timeZoneBot.tz }}
|
||||
@@ -106,7 +107,7 @@
|
||||
await settingsStore.updateSettings({ fontSize: newValue })
|
||||
})
|
||||
|
||||
const timeZoneBot = ref<{ tz: string, offset: number }>({ tz: '', offset: 1 })
|
||||
const timeZoneBot = ref<{ tz: string, offset: number,offsetString: string }>()
|
||||
|
||||
watch(timeZoneBot, async (newValue) => {
|
||||
if (newValue) await settingsStore.updateSettings({ timeZoneBot: newValue })
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
:key="item.id"
|
||||
@right="handleSlide($event, item.id)"
|
||||
right-color="red"
|
||||
@click="goChat(item.invite_link)"
|
||||
@click="goChatInfo(item.id)"
|
||||
>
|
||||
<template #right>
|
||||
<q-icon size="lg" name="mdi-link-off"/>
|
||||
@@ -169,6 +169,11 @@
|
||||
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('')
|
||||
@@ -259,8 +264,8 @@
|
||||
tg.openTelegramLink(tgShareUrl)
|
||||
}
|
||||
|
||||
function goChat (invite: string) {
|
||||
tg.openTelegramLink(invite)
|
||||
async function goChatInfo (chatId: number) {
|
||||
await router.push({ name: 'chat_info', params: { id: route.params.id, chatId }})
|
||||
}
|
||||
|
||||
// fix fab jumping
|
||||
@@ -273,7 +278,6 @@
|
||||
}, 300)
|
||||
})
|
||||
|
||||
|
||||
onDeactivated(() => {
|
||||
showFab.value = false
|
||||
if (timerId.value) {
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useUsersStore } from 'stores/users'
|
||||
import { useCompaniesStore } from 'stores/companies'
|
||||
import type { User } from 'types/Users'
|
||||
import { useUserSection } from 'composables/useUserSection'
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
const router = useRouter()
|
||||
@@ -255,6 +255,7 @@
|
||||
const showDialogBlockUser = ref<boolean>(false)
|
||||
const currentSlideEvent = ref<SlideEvent | null>(null)
|
||||
const closedByUserAction = ref(false)
|
||||
const { userSection } = useUserSection()
|
||||
|
||||
const mapUsers = computed(() => users.map(el => ({
|
||||
...el,
|
||||
@@ -284,35 +285,6 @@
|
||||
|
||||
const displayUsers = computed(() => displayUsersAll.value.filter(el => !el.is_blocked))
|
||||
|
||||
function userSection (user: User) {
|
||||
const tname = () => {
|
||||
return user.firstname
|
||||
? user.lastname
|
||||
? user.firstname + ' ' + user.lastname
|
||||
: user.firstname
|
||||
: user.lastname ?? ''
|
||||
}
|
||||
|
||||
const section1 = user.fullname ?? tname()
|
||||
|
||||
const section2_1 = user.fullname ? tname() : ''
|
||||
|
||||
const section2_2 = user.username ?? ''
|
||||
|
||||
const section3 = (
|
||||
user.company_id && companiesStore.companyById(user.company_id)
|
||||
? companiesStore.companyById(user.company_id)?.name + ((user.role || user.department ) ? ' / ' :'')
|
||||
: '') +
|
||||
(user.department ? user.department + (user.role ? ' / ' : '') : '') +
|
||||
(user.role ?? '')
|
||||
|
||||
return {
|
||||
section1,
|
||||
section2_1, section2_2,
|
||||
section3
|
||||
}
|
||||
}
|
||||
|
||||
async function goUserInfo (id: number) {
|
||||
await router.push({ name: 'user_info', params: { id: route.params.id, userId: id }})
|
||||
}
|
||||
|
||||
@@ -75,6 +75,12 @@ const routes: RouteRecordRaw[] = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'chat_info',
|
||||
path: '/project/:id(\\d+)/chat/:chatId',
|
||||
component: () => import('pages/ChatPage.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
name: 'add_company',
|
||||
path: '/project/:id(\\d+)/add-company',
|
||||
|
||||
@@ -26,18 +26,23 @@ export const useChatsStore = defineStore('chats', () => {
|
||||
}
|
||||
|
||||
async function unlink (chatId: number) {
|
||||
const response = await api.get('/project/' + currentProjectId.value + '/chat/' + chatId)
|
||||
const chatAPIid = response.data.data.id
|
||||
const { data } = await api.get('/project/' + currentProjectId.value + '/chat/' + chatId)
|
||||
const chatAPIid = data.data.id
|
||||
const idx = chats.value.findIndex(item => item.id === chatAPIid)
|
||||
chats.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
async function getKey () {
|
||||
const response = await api.get('/project/' + currentProjectId.value + '/token')
|
||||
const key = <string>response.data.data
|
||||
const { data } = await api.get('/project/' + currentProjectId.value + '/token')
|
||||
const key = <string>data.data
|
||||
return key
|
||||
}
|
||||
|
||||
async function getChatUsers (chatId: number) {
|
||||
const { data } = await api.get('/project/' + currentProjectId.value + '/chat/' + chatId)
|
||||
console.log(222, data)
|
||||
}
|
||||
|
||||
const getChats = computed(() => chats.value)
|
||||
|
||||
function chatById (id: number) {
|
||||
@@ -58,6 +63,7 @@ export const useChatsStore = defineStore('chats', () => {
|
||||
unlink,
|
||||
getKey,
|
||||
getChats,
|
||||
chatById
|
||||
chatById,
|
||||
getChatUsers
|
||||
}
|
||||
})
|
||||
|
||||
@@ -80,11 +80,12 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
if (authStore.isAuth) {
|
||||
try {
|
||||
const { data } = await api.get('/customer/settings')
|
||||
console.log(data.data)
|
||||
settings.value = {
|
||||
fontSize: data.data.settings.fontSize || defaultSettings.fontSize,
|
||||
locale: data.data.settings.locale || detectLocale(),
|
||||
timeZoneBot: data.data.settings.timeZone || defaultSettings.timeZoneBot,
|
||||
localeBot: data.data.settings.localeBot || detectLocale()
|
||||
fontSize: data.data.fontSize || defaultSettings.fontSize,
|
||||
locale: data.data.locale || detectLocale(),
|
||||
timeZoneBot: data.data.timeZone || defaultSettings.timeZoneBot,
|
||||
localeBot: data.data.localeBot || detectLocale()
|
||||
}
|
||||
} catch {
|
||||
settings.value.locale = detectLocale()
|
||||
@@ -101,7 +102,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
}
|
||||
|
||||
const saveSettings = async () => {
|
||||
await api.put('/customer/settings', { settings: settings.value })
|
||||
await api.put('/customer/settings', settings.value)
|
||||
}
|
||||
|
||||
const updateSettings = async (newSettings: Partial<AppSettings>) => {
|
||||
|
||||
@@ -11,7 +11,8 @@ interface Chat {
|
||||
logo: string | null
|
||||
owner_id?: number
|
||||
invite_link: string
|
||||
[key: string]: unknown
|
||||
chat_users: number []
|
||||
[key: string]: number | string | boolean | null | number[]
|
||||
}
|
||||
|
||||
export type {
|
||||
|
||||
Reference in New Issue
Block a user