change
This commit is contained in:
BIN
i18n-2.xlsm
BIN
i18n-2.xlsm
Binary file not shown.
33
src/App.vue
33
src/App.vue
@@ -3,10 +3,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, inject } from 'vue'
|
import { onMounted, inject, onUnmounted, watch } from 'vue'
|
||||||
import { useSettingsStore } from 'stores/settings'
|
import { useSettingsStore } from 'stores/settings'
|
||||||
|
import { useAuthStore } from 'stores/auth'
|
||||||
|
import { useSSEStore } from 'stores/SSE'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import type { WebApp } from '@twa-dev/types'
|
import type { WebApp } from '@twa-dev/types'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import type { CompanyParams } from 'types/Company'
|
||||||
|
|
||||||
|
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
|
const sseStore = useSSEStore()
|
||||||
|
const { isAuth } = storeToRefs(authStore)
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const tg = inject('tg') as WebApp
|
const tg = inject('tg') as WebApp
|
||||||
@@ -15,10 +27,27 @@
|
|||||||
await router.push({ name: 'account' })
|
await router.push({ name: 'account' })
|
||||||
})
|
})
|
||||||
|
|
||||||
const settingsStore = useSettingsStore()
|
watch(isAuth, async (newVal: boolean) => {
|
||||||
|
if (newVal) sseStore.connect()
|
||||||
|
else sseStore.disconnect()
|
||||||
|
|
||||||
|
if (authStore.customer?.company ||
|
||||||
|
(authStore.customer?.company &&
|
||||||
|
Object.keys(authStore.customer?.company).length === 0
|
||||||
|
)) {
|
||||||
|
await authStore.updateMyCompany({ name: t('company_edit__my_company')} as CompanyParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newVal) await settingsStore.init()
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await settingsStore.init()
|
await settingsStore.init()
|
||||||
|
if (isAuth.value) sseStore.connect()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
sseStore.disconnect()
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
{{ $t(title) }}
|
{{ $t(title) }}
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="w100 q-pa-md">
|
|
||||||
<q-btn
|
<q-btn
|
||||||
rounded color="primary"
|
rounded color="primary"
|
||||||
class="w100 fix-disabled-btn"
|
class="w100 fix-disabled-btn"
|
||||||
@@ -13,7 +12,6 @@
|
|||||||
>
|
>
|
||||||
{{ $t(btnText) }}
|
{{ $t(btnText) }}
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<pn-scroll-list>
|
<pn-scroll-list>
|
||||||
@@ -62,7 +60,7 @@
|
|||||||
import { computed, reactive } from 'vue'
|
import { computed, reactive } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import type { CompanyParams } from 'types/Company'
|
import type { CompanyParams } from 'types/Company'
|
||||||
import { removeNullKeys } from 'utils/helpers'
|
import { removeNullKeys, getFieldValue } from 'utils/helpers'
|
||||||
|
|
||||||
const { t }= useI18n()
|
const { t }= useI18n()
|
||||||
|
|
||||||
@@ -122,13 +120,14 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
function onSubmit() {
|
function onSubmit() {
|
||||||
|
// companyMod.description! - ! on end for TS, i promise: params !== null | undefined
|
||||||
const outData = {
|
const outData = {
|
||||||
name: companyMod.name,
|
name: companyMod.name,
|
||||||
description: companyMod.description ?? (typeof props.company?.description === 'string' ? '' : null),
|
description: getFieldValue(companyMod.description!, props.company?.description),
|
||||||
site: companyMod.site ?? (typeof props.company?.site === 'string' ? '' : null),
|
site: getFieldValue(companyMod.site!, props.company?.site),
|
||||||
address: companyMod.address ?? (typeof props.company?.address === 'string' ? '' : null),
|
address: getFieldValue(companyMod.address!, props.company?.address),
|
||||||
phone: companyMod.phone ?? (typeof props.company?.phone === 'string' ? '' : null),
|
phone: getFieldValue(companyMod.phone!, props.company?.phone),
|
||||||
email: companyMod.email ?? (typeof props.company?.email === 'string' ? '' : null),
|
email: getFieldValue(companyMod.email!, props.company?.email),
|
||||||
logo: companyMod.logo
|
logo: companyMod.logo
|
||||||
}
|
}
|
||||||
emit('update', removeNullKeys(outData))
|
emit('update', removeNullKeys(outData))
|
||||||
|
|||||||
89
src/components/pnActionBar.vue
Normal file
89
src/components/pnActionBar.vue
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="flex row q-mx-sm justify-between items-center no-wrap q-py-sm"
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
v-if="showCalendarBtn"
|
||||||
|
flat round
|
||||||
|
:color="calendarActive ? 'primary' : 'grey'"
|
||||||
|
@click="emit('toggle-calendar')"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<q-icon name="mdi-calendar-month-outline" size="sm"/>
|
||||||
|
<q-badge
|
||||||
|
color="red"
|
||||||
|
rounded
|
||||||
|
floating
|
||||||
|
transparent
|
||||||
|
style="position: relative; top: -6px; margin-left: -12px"
|
||||||
|
:style="{ opacity: calendarBadge ? 0.8 : 0 }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
v-model="search"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
borderless
|
||||||
|
filled
|
||||||
|
:placeholder="$t(placeholder)"
|
||||||
|
dense
|
||||||
|
class="col-grow q-pt-xs q-mx-sm"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<q-icon name="mdi-magnify" color="grey"/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
v-if="showFilterBtn"
|
||||||
|
@click="emit('open-filters')"
|
||||||
|
flat round
|
||||||
|
:color="filterActive ? 'primary' : 'grey'"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<q-icon name="mdi-filter-outline" size="sm"/>
|
||||||
|
<q-badge
|
||||||
|
color="red"
|
||||||
|
rounded
|
||||||
|
floating
|
||||||
|
transparent
|
||||||
|
style="position: relative; top: -6px; margin-left: -12px"
|
||||||
|
:style="{ opacity: filterBadge ? 0.8 : 0 }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const search = defineModel<string>({
|
||||||
|
required: true,
|
||||||
|
default: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: 'Search...'
|
||||||
|
},
|
||||||
|
showCalendarBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
showFilterBtn: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
calendarActive: Boolean,
|
||||||
|
filterActive: Boolean,
|
||||||
|
calendarBadge: Boolean,
|
||||||
|
filterBadge: Boolean
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits([
|
||||||
|
'toggle-calendar',
|
||||||
|
'open-filters'
|
||||||
|
])
|
||||||
|
</script>
|
||||||
97
src/components/pnBottomSheetDialog.vue
Normal file
97
src/components/pnBottomSheetDialog.vue
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<q-dialog
|
||||||
|
v-model="modelValue"
|
||||||
|
maximized
|
||||||
|
transition-show="slide-up"
|
||||||
|
transition-hide="slide-down"
|
||||||
|
position="bottom"
|
||||||
|
>
|
||||||
|
<q-card
|
||||||
|
class="fix-card-width flex column no-scroll no-wrap q-px-none"
|
||||||
|
style="
|
||||||
|
border-top-left-radius: var(--top-radius) !important;
|
||||||
|
border-top-right-radius: var(--top-radius) !important;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref="cardHeaderRef"
|
||||||
|
class="flex items-center no-wrap justify-between w100 q-my-none q-pa-md"
|
||||||
|
>
|
||||||
|
<div class="text-h6 q-mx-xs ellipsis">{{ $t(title) }}</div>
|
||||||
|
<div class="flex items-center justify-between no-wrap">
|
||||||
|
<slot name="btnSlot">
|
||||||
|
<q-btn
|
||||||
|
icon="mdi-close"
|
||||||
|
@click="modelValue=false"
|
||||||
|
flat round
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref="cardBodyRef"
|
||||||
|
class="q-px-none q-ma-none"
|
||||||
|
>
|
||||||
|
<pn-shadow-scroll
|
||||||
|
:hideShadows="false"
|
||||||
|
:height="bodyHeight"
|
||||||
|
>
|
||||||
|
<div ref="cardBodyInnerRef" class="q-px-md q-ma-none">
|
||||||
|
<q-resize-observer @resize="updateDimensions" />
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</pn-shadow-scroll>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref="cardFooterRef"
|
||||||
|
class="q-pa-md"
|
||||||
|
>
|
||||||
|
<slot name="footer"/>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useSlots } from 'vue'
|
||||||
|
|
||||||
|
const modelValue = defineModel<boolean>({
|
||||||
|
required: true
|
||||||
|
})
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
title: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
const cardHeaderRef = ref<HTMLElement | null>(null)
|
||||||
|
const cardFooterRef = ref<HTMLElement | null>(null)
|
||||||
|
const cardBodyRef = ref<HTMLElement | null>(null)
|
||||||
|
const cardBodyInnerRef = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
const headerHeight = ref(0)
|
||||||
|
const footerHeight = ref(0)
|
||||||
|
const bodyInnerHeight = ref(0)
|
||||||
|
const bodyHeight = ref(0)
|
||||||
|
|
||||||
|
const updateDimensions = () => {
|
||||||
|
headerHeight.value = cardHeaderRef.value?.offsetHeight || 0
|
||||||
|
footerHeight.value = cardFooterRef.value?.offsetHeight || 0
|
||||||
|
bodyInnerHeight.value = cardBodyInnerRef.value?.offsetHeight || 0
|
||||||
|
const needScroll = headerHeight.value + footerHeight.value + bodyInnerHeight.value > window.innerHeight - 48
|
||||||
|
bodyHeight.value = needScroll
|
||||||
|
? window.innerHeight - 48 - headerHeight.value - footerHeight.value
|
||||||
|
: bodyInnerHeight.value + 16
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => slots.body?.(), updateDimensions, { flush: 'post' })
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fix-card-width {
|
||||||
|
width: var(--body-width) !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
45
src/components/pnChatTypeItem.vue
Normal file
45
src/components/pnChatTypeItem.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<q-item>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar
|
||||||
|
rounded
|
||||||
|
text-color="white"
|
||||||
|
:icon="chatType.icon"
|
||||||
|
:color="chatType.color"
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>
|
||||||
|
<div class="row no-wrap items-center justify-between">
|
||||||
|
{{ $t(chatType.label) }}
|
||||||
|
<span class="text-caption text-grey" v-if="time">
|
||||||
|
{{ date.formatDate(time * 1000, 'DD MMM YYYY') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-label class="text-grey text-caption">
|
||||||
|
{{ $t(chatType.description) }}
|
||||||
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { getChatType } from 'utils/chats'
|
||||||
|
import { date } from 'quasar'
|
||||||
|
import type { ChatType } from 'types/Chat'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
type: ChatType
|
||||||
|
time?: number
|
||||||
|
}>(), {
|
||||||
|
type: 'full'
|
||||||
|
})
|
||||||
|
|
||||||
|
const chatType = computed(() => getChatType(props.type))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -6,12 +6,18 @@
|
|||||||
<slot name="title"/>
|
<slot name="title"/>
|
||||||
</div>
|
</div>
|
||||||
<slot/>
|
<slot/>
|
||||||
<div class="bg-white w100">
|
<div v-if="hasFooter" class="flex w100 q-pa-md bg-white">
|
||||||
<slot name="footer"/>
|
<slot name="footer"/>
|
||||||
</div>
|
</div>
|
||||||
|
<slot v-if="hasTabs" name="tabs"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useSlots, computed } from 'vue'
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
const hasFooter = computed(() => !!slots.footer)
|
||||||
|
const hasTabs = computed(() => !!slots.tabs)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="card-body-header"
|
id="card-body-header"
|
||||||
v-if="slots['card-body-header']"
|
v-if="!hideHeader && hasHeader"
|
||||||
style="flex-shrink: 0; min-height: var(--top-radius);"
|
style="flex-shrink: 0; min-height: var(--top-radius);"
|
||||||
>
|
>
|
||||||
<q-resize-observer @resize="onHeaderResize"/>
|
<q-resize-observer @resize="onHeaderResize"/>
|
||||||
@@ -13,25 +13,39 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="card-body"
|
<div id="card-body"
|
||||||
class="top-rounded-card bg-white"
|
class="top-rounded-card bg-white"
|
||||||
style="padding-top: var(--top-radius);"
|
|
||||||
>
|
>
|
||||||
|
<div style="min-height: var(--top-radius);">
|
||||||
|
<slot name="card-body-btn"/>
|
||||||
|
<q-resize-observer @resize="onBodyBtnResize"/>
|
||||||
|
</div>
|
||||||
|
<div style="height:100%">
|
||||||
<q-resize-observer @resize="onBodyResize"/>
|
<q-resize-observer @resize="onBodyResize"/>
|
||||||
<pn-shadow-scroll :hideShadows="isResizing" :height="scrollAreaHeight">
|
<pn-shadow-scroll :hideShadows="isResizing" :height="scrollAreaHeight">
|
||||||
<slot/>
|
<slot/>
|
||||||
</pn-shadow-scroll>
|
</pn-shadow-scroll>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, nextTick } from 'vue'
|
import { ref, watch, nextTick, useSlots, computed } from 'vue'
|
||||||
import { useSlots } from 'vue'
|
|
||||||
|
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
|
// work only slot no use in component
|
||||||
|
const hasHeader = computed(() => !!slots['card-body-header'])
|
||||||
|
|
||||||
|
// need if slot draw by expression with v-if
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
hideHeader?: boolean
|
||||||
|
}>(), {
|
||||||
|
hideHeader: false
|
||||||
|
})
|
||||||
|
|
||||||
const heightCard = ref(100)
|
const heightCard = ref(100)
|
||||||
const scrollAreaHeight = ref(100)
|
const scrollAreaHeight = ref(100)
|
||||||
const headerHeight = ref(0)
|
const headerHeight = ref(0)
|
||||||
|
const headerBtnHeight = ref(0)
|
||||||
|
|
||||||
interface sizeParams {
|
interface sizeParams {
|
||||||
height: number,
|
height: number,
|
||||||
@@ -43,8 +57,12 @@ async function onHeaderResize(size: sizeParams) {
|
|||||||
await updateScrollAreaHeight()
|
await updateScrollAreaHeight()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onBodyBtnResize (size: sizeParams) {
|
||||||
|
headerBtnHeight.value = size.height
|
||||||
|
}
|
||||||
|
|
||||||
async function onBodyResize (size: sizeParams) {
|
async function onBodyResize (size: sizeParams) {
|
||||||
heightCard.value = size.height - parseInt(String(getComputedStyle(document.documentElement).getPropertyValue('--top-radius'))) // subtract padding var(top-radius)
|
heightCard.value = size.height - headerBtnHeight.value
|
||||||
await updateScrollAreaHeight()
|
await updateScrollAreaHeight()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
73
src/components/pnSimpleScrollDialog.vue
Normal file
73
src/components/pnSimpleScrollDialog.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<q-dialog v-model="modelValue">
|
||||||
|
<q-card
|
||||||
|
class="q-pa-none q-ma-none w100 no-scroll"
|
||||||
|
>
|
||||||
|
<q-card-section class="row items-center q-pb-none">
|
||||||
|
<div class="flex no-wrap items-center w100">
|
||||||
|
<q-resize-observer @resize="onHeaderResize"/>
|
||||||
|
<span class="text-bold">
|
||||||
|
{{ $t(title)}}
|
||||||
|
</span>
|
||||||
|
<q-space />
|
||||||
|
<q-btn icon="close" flat round dense v-close-popup />
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<pn-shadow-scroll :height="scrollAreaHeight" :hideShadows="false">
|
||||||
|
<div>
|
||||||
|
<q-resize-observer @resize="onBodyResize"/>
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</pn-shadow-scroll>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, nextTick } from 'vue'
|
||||||
|
import pnShadowScroll from './pnShadowScroll.vue'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
title: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const modelValue = defineModel<boolean>({
|
||||||
|
required: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const headerHeight = ref(0)
|
||||||
|
const bodyHeight = ref(0)
|
||||||
|
const scrollAreaHeight = ref(0)
|
||||||
|
|
||||||
|
interface sizeParams {
|
||||||
|
height: number,
|
||||||
|
width: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHeaderResize (size: sizeParams) {
|
||||||
|
headerHeight.value = size.height
|
||||||
|
}
|
||||||
|
|
||||||
|
function onBodyResize (size: sizeParams) {
|
||||||
|
bodyHeight.value = size.height
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateScrollAreaHeight () {
|
||||||
|
await nextTick(() => {
|
||||||
|
scrollAreaHeight.value = Math.min(
|
||||||
|
bodyHeight.value + 16,
|
||||||
|
window.innerHeight - headerHeight.value - 64
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(bodyHeight, updateScrollAreaHeight)
|
||||||
|
watch(headerHeight, updateScrollAreaHeight)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
{{ $t(title) }}
|
{{ $t(title) }}
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="w100 q-pa-md">
|
|
||||||
<q-btn
|
<q-btn
|
||||||
rounded color="primary"
|
rounded color="primary"
|
||||||
class="w100 fix-disabled-btn"
|
class="w100 fix-disabled-btn"
|
||||||
@@ -13,7 +12,6 @@
|
|||||||
>
|
>
|
||||||
{{ $t(btnText) }}
|
{{ $t(btnText) }}
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<pn-scroll-list>
|
<pn-scroll-list>
|
||||||
@@ -59,7 +57,7 @@
|
|||||||
import { computed, reactive } from 'vue'
|
import { computed, reactive } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import type { ProjectParams } from 'types/Project'
|
import type { ProjectParams } from 'types/Project'
|
||||||
import { removeNullKeys } from 'utils/helpers'
|
import { removeNullKeys, getFieldValue } from 'utils/helpers'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -96,7 +94,7 @@
|
|||||||
function onSubmit() {
|
function onSubmit() {
|
||||||
const outData = {
|
const outData = {
|
||||||
name: projectMod.name,
|
name: projectMod.name,
|
||||||
description: projectMod.description ?? (typeof props.project?.description === 'string' ? '' : null),
|
description: getFieldValue(projectMod.description, props.project?.description),
|
||||||
logo: projectMod.logo
|
logo: projectMod.logo
|
||||||
}
|
}
|
||||||
emit('update', removeNullKeys(outData))
|
emit('update', removeNullKeys(outData))
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
{{ $t(title) }}
|
{{ $t(title) }}
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="w100 q-pa-md">
|
|
||||||
<q-btn
|
<q-btn
|
||||||
rounded color="primary"
|
rounded color="primary"
|
||||||
class="w100 fix-disabled-btn"
|
class="w100 fix-disabled-btn"
|
||||||
@@ -12,12 +11,10 @@
|
|||||||
>
|
>
|
||||||
{{ $t(btnText) }}
|
{{ $t(btnText) }}
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<pn-scroll-list>
|
<pn-scroll-list>
|
||||||
<div class="flex column items-center q-pa-md q-pb-sm">
|
<div class="column items-center q-pa-md q-pb-sm">
|
||||||
<div class="relative-position">
|
<div class="relative-position flex justify-center w100 text-center">
|
||||||
<pn-auto-avatar
|
<pn-auto-avatar
|
||||||
:img="userMod.photo"
|
:img="userMod.photo"
|
||||||
:name="tname"
|
:name="tname"
|
||||||
@@ -27,10 +24,15 @@
|
|||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="userStatus"
|
v-if="userStatus"
|
||||||
class="absolute-center text-h4 text-bold q-pa-sm text-center"
|
class="flex justify-center absolute-center w100 items-center"
|
||||||
:class ="'status-' + userStatus.status"
|
|
||||||
>
|
>
|
||||||
{{ $t(userStatus.text) }}
|
<q-chip
|
||||||
|
square
|
||||||
|
:label="$t(userStatus.text)"
|
||||||
|
:color="userStatus.color"
|
||||||
|
text-color="white"
|
||||||
|
class="text-uppercase"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex row items-start justify-center no-wrap q-pb-lg">
|
<div class="flex row items-start justify-center no-wrap q-pb-lg">
|
||||||
@@ -134,7 +136,8 @@
|
|||||||
import { computed, reactive } from 'vue'
|
import { computed, reactive } from 'vue'
|
||||||
import { useCompaniesStore } from 'stores/companies'
|
import { useCompaniesStore } from 'stores/companies'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { removeNullKeys } from 'utils/helpers'
|
import { removeNullKeys, getFieldValue } from 'utils/helpers'
|
||||||
|
import { getUserStatus } from 'utils/users'
|
||||||
import type { User } from 'types/User'
|
import type { User } from 'types/User'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -179,21 +182,16 @@
|
|||||||
.join(' ')
|
.join(' ')
|
||||||
)
|
)
|
||||||
|
|
||||||
const userStatus = computed(() => {
|
const userStatus = computed(() => props.user ? getUserStatus(props.user) : null)
|
||||||
if (props.user?.is_blocked) return { status: 'blocked', text: 'user_block__user_blocked' }
|
|
||||||
if (props.user?.is_leave) return { status: 'leave', text: 'user_block__user_leave' }
|
|
||||||
if (!props.user?.is_terms_accepted) return { status: 'pending', text: 'user_block__user_pending' }
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
function onSubmit() {
|
function onSubmit() {
|
||||||
const outData = {
|
const outData = {
|
||||||
fullname: userMod.fullname ?? (typeof props.user?.fullname === 'string' ? '' : null),
|
fullname: getFieldValue(userMod.fullname, props.user?.fullname),
|
||||||
company_id: userMod.company_id,
|
company_id: userMod.company_id,
|
||||||
department: userMod.department ?? (typeof props.user?.department === 'string' ? '' : null),
|
department: getFieldValue(userMod.department, props.user?.department),
|
||||||
role: userMod.role ?? (typeof props.user?.role === 'string' ? '' : null),
|
role: getFieldValue(userMod.role, props.user?.role),
|
||||||
phone: userMod.phone ?? (typeof props.user?.phone === 'string' ? '' : null),
|
phone: getFieldValue(userMod.phone, props.user?.phone),
|
||||||
email: userMod.email ?? (typeof props.user?.email === 'string' ? '' : null),
|
email: getFieldValue(userMod.email, props.user?.email),
|
||||||
photo: userMod.photo
|
photo: userMod.photo
|
||||||
}
|
}
|
||||||
emit('update', removeNullKeys(outData))
|
emit('update', removeNullKeys(outData))
|
||||||
@@ -205,19 +203,4 @@
|
|||||||
.fix-bottom-padding.q-field--with-bottom {
|
.fix-bottom-padding.q-field--with-bottom {
|
||||||
padding-bottom: 0 !important
|
padding-bottom: 0 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-blocked {
|
|
||||||
border: 2px solid var(--q-negative);
|
|
||||||
color: var(--q-negative);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-leave {
|
|
||||||
border: 2px solid var(--q-primary);
|
|
||||||
color: var(--q-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-pending{
|
|
||||||
border: 2px solid var(--q-secondary);
|
|
||||||
color: var(--q-secondary);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ export function useUserSection() {
|
|||||||
? user.firstname + ' ' + user.lastname
|
? user.firstname + ' ' + user.lastname
|
||||||
: user.firstname
|
: user.firstname
|
||||||
: user.lastname ?? ''
|
: user.lastname ?? ''
|
||||||
};
|
}
|
||||||
|
|
||||||
const section1 = user.fullname ?? tname()
|
const section1 = user.fullname && user.fullname !== '' ? user.fullname : tname()
|
||||||
const section2_1 = user.fullname ? tname() : ''
|
const section2_1 = user.fullname ? tname() : ''
|
||||||
const section2_2 = user.username ?? ''
|
const section2_2 = user.username ?? ''
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<pn-scroll-list>
|
<pn-scroll-list>
|
||||||
<div class="flex column w100 q-py-md q-px-lg q-gutter-y-sm text-caption">
|
<div class="flex column w100 q-py-md q-px-lg q-gutter-y-sm text-caption">
|
||||||
<div class="text-bold">
|
<div class="text-body1">
|
||||||
<div v-if="route.query.type === 'update'">{{ $t('agreements__description_update') }}</div>
|
<div v-if="route.query.type === 'update'">{{ $t('agreements__description_update') }}</div>
|
||||||
<div>{{ $t('agreements__description') }}</div>
|
<div>{{ $t('agreements__description') }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,31 +43,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</pn-scroll-list>
|
</pn-scroll-list>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="flex no-wrap justify-center w100 q-gutter-x-md q-py-md">
|
<div class="column no-wrap items-center w100">
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
no-caps rounded
|
||||||
|
:disable="agreement.length !== 2"
|
||||||
|
@click="onSubmit"
|
||||||
|
class="fix-disabled-btn w100"
|
||||||
|
>
|
||||||
|
{{$t('agreements__btn_agree')}}
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
color="negative"
|
color="negative"
|
||||||
no-caps
|
no-caps rounded
|
||||||
style="opacity: 0.6"
|
style="opacity: 0.6"
|
||||||
@click="onDecline"
|
@click="onDecline"
|
||||||
class="col"
|
class="q-mt-md w100"
|
||||||
>
|
>
|
||||||
<div class="flex column items-center">
|
<div class="flex column items-center">
|
||||||
{{ $t('agreements__btn_decline') }}
|
{{ $t('agreements__btn_decline') }}
|
||||||
<span class="text-caption">
|
|
||||||
{{ $t('agreements__btn_decline_description') }}
|
{{ $t('agreements__btn_decline_description') }}
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn
|
|
||||||
color="primary"
|
|
||||||
no-caps
|
|
||||||
:disable="agreement.length !== 2"
|
|
||||||
@click="onSubmit"
|
|
||||||
class="fix-disabled-btn col"
|
|
||||||
>
|
|
||||||
{{$t('agreements__btn_agree')}}
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
{{ $t('chat_card__title') }}
|
{{ $t('chat_card__title') }}
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="w100 q-pa-md">
|
|
||||||
<q-btn
|
<q-btn
|
||||||
rounded color="primary"
|
rounded color="primary"
|
||||||
class="w100"
|
class="w100"
|
||||||
@@ -13,13 +12,12 @@
|
|||||||
>
|
>
|
||||||
{{ $t("chat_card__go_chat") }}
|
{{ $t("chat_card__go_chat") }}
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<pn-scroll-list>
|
<pn-scroll-list>
|
||||||
<div
|
<div
|
||||||
v-if="chat"
|
v-if="chat"
|
||||||
class="flex column items-center q-pa-md"
|
class="flex column items-center q-px-md q-pt-md q-pb-sm"
|
||||||
>
|
>
|
||||||
<pn-auto-avatar
|
<pn-auto-avatar
|
||||||
:img="chat.logo"
|
:img="chat.logo"
|
||||||
@@ -34,11 +32,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="chat.description"
|
v-if="chat.description"
|
||||||
class="flex row items-start justify-center text-caption text-center"
|
class="flex row items-start justify-center text-caption text-center text-grey"
|
||||||
>
|
>
|
||||||
{{ chat.description }}
|
{{ chat.description }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="q-pb-md w100" v-if="chat">
|
||||||
|
<pn-chat-type-item :type="chat.bot_access || 0" :time="chat.bot_access_date"/>
|
||||||
|
<pn-chat-type-item v-if="chat.restore_date" :type="10" :time="chat.restore_date"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<q-list separator v-if="chatUsers.length !== 0">
|
<q-list separator v-if="chatUsers.length !== 0">
|
||||||
<q-item-label class="text-caption text-bold q-py-none" header>
|
<q-item-label class="text-caption text-bold q-py-none" header>
|
||||||
{{ $t('chat_card__members') + ' (' + chatUsers.length +')' }}
|
{{ $t('chat_card__members') + ' (' + chatUsers.length +')' }}
|
||||||
@@ -58,9 +61,15 @@
|
|||||||
/>
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label lines="1" v-if="getUserStatus(item).status">
|
<q-item-label class="text-caption text-amber-10" lines="1" v-if="chat?.owner_id === item.id">
|
||||||
<q-badge :color="getUserStatus(item).color">
|
<div class="flex items-center">
|
||||||
{{ $t(getUserStatus(item).text) }}
|
<q-icon name="star" class="q-pr-xs"/>
|
||||||
|
{{ $t('chat_card__owner') }}
|
||||||
|
</div>
|
||||||
|
</q-item-label>
|
||||||
|
<q-item-label lines="1" v-if="getUserStatus(item) && getUserStatus(item)?.status">
|
||||||
|
<q-badge :color="getUserStatus(item)?.color">
|
||||||
|
{{ $t(getUserStatus(item)?.text ?? '') }}
|
||||||
</q-badge>
|
</q-badge>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label lines="1" class="text-bold" v-if="item.section1">
|
<q-item-label lines="1" class="text-bold" v-if="item.section1">
|
||||||
@@ -91,10 +100,11 @@
|
|||||||
import { useUsersStore } from 'stores/users'
|
import { useUsersStore } from 'stores/users'
|
||||||
import { useCompaniesStore } from 'stores/companies'
|
import { useCompaniesStore } from 'stores/companies'
|
||||||
import { parseIntString } from 'src/utils/helpers'
|
import { parseIntString } from 'src/utils/helpers'
|
||||||
import type { Chat } from 'types/Chat'
|
|
||||||
import type { User } from 'types/User'
|
|
||||||
import type { WebApp } from '@twa-dev/types'
|
|
||||||
import { useUserSection } from 'composables/useUserSection'
|
import { useUserSection } from 'composables/useUserSection'
|
||||||
|
import pnChatTypeItem from 'components/pnChatTypeItem.vue'
|
||||||
|
import { getUserStatus } from 'utils/users'
|
||||||
|
import type { Chat } from 'types/Chat'
|
||||||
|
import type { WebApp } from '@twa-dev/types'
|
||||||
|
|
||||||
const tg = inject('tg') as WebApp
|
const tg = inject('tg') as WebApp
|
||||||
|
|
||||||
@@ -119,9 +129,8 @@
|
|||||||
|
|
||||||
watch(() => chatsStore.isInit, initChat)
|
watch(() => chatsStore.isInit, initChat)
|
||||||
|
|
||||||
async function onSubmit () {
|
function onSubmit () {
|
||||||
if (chat.value) tg.openTelegramLink('https://t.me/c/' + chat.value.telegram_id + '/' + chat.value.message_id)
|
if (chat.value) tg.openTelegramLink('https://t.me/c/' + chat.value.telegram_id + '/' + chat.value.message_id)
|
||||||
if (chat.value) await chatsStore.getChatUsers(chat.value.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = computed(() => usersStore.users)
|
const users = computed(() => usersStore.users)
|
||||||
@@ -146,19 +155,4 @@
|
|||||||
await router.push({ name: 'user_info', params: { id: route.params.id, userId: id }})
|
await router.push({ name: 'user_info', params: { id: route.params.id, userId: id }})
|
||||||
}
|
}
|
||||||
|
|
||||||
interface chatUser extends User {
|
|
||||||
section1: string
|
|
||||||
section2_1: string
|
|
||||||
section2_2: string
|
|
||||||
section3: string
|
|
||||||
companyName: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUserStatus (item: chatUser) {
|
|
||||||
if (item.is_blocked) return { status: 'blocked', text: 'user_block__user_blocked', color: 'negative' }
|
|
||||||
if (item.is_leave) return { status: 'leave', text: 'user_block__user_leave', color: 'primary' }
|
|
||||||
if (!item.is_terms_accepted) return { status: 'pending', text: 'user_block__user_pending', color: 'secondary' }
|
|
||||||
return { status: null, text: '', color: '' }
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<q-btn
|
<q-btn
|
||||||
rounded color="primary"
|
rounded color="primary"
|
||||||
class="w100 q-mt-md q-mb-xs fix-disabled-btn"
|
class="w100 fix-disabled-btn"
|
||||||
@click="updateCompanies()"
|
@click="updateCompanies()"
|
||||||
>
|
>
|
||||||
{{ $t('mask__btn_ok') }}
|
{{ $t('mask__btn_ok') }}
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
<pn-scroll-list>
|
<pn-scroll-list>
|
||||||
<template #card-body-header>
|
<template #card-body-header>
|
||||||
<div class="q-ma-sm q-pa-sm bg-grey-11 flex no-wrap items-center" style="border-radius: var(--top-radius);">
|
<div class="q-ma-md flex no-wrap items-center">
|
||||||
<q-icon name="mdi-information q-pr-xs" size="sm" color="primary"/>
|
<q-icon name="mdi-information q-pr-sm" size="sm" color="primary"/>
|
||||||
<span class="text-caption">
|
<span class="text-caption">
|
||||||
{{ $t('mask__info_block')}}
|
{{ $t('mask__info_block')}}
|
||||||
</span>
|
</span>
|
||||||
@@ -198,7 +198,7 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
function updateDisplayCompanies() {
|
function updateDisplayCompanies() {
|
||||||
if (companies.value.length === 0 || companiesMask.value.length === 0) return
|
if (companies.value.length === 0) return
|
||||||
|
|
||||||
displayCompanies.value = companies.value
|
displayCompanies.value = companies.value
|
||||||
.filter(el => !el.is_own)
|
.filter(el => !el.is_own)
|
||||||
|
|||||||
@@ -197,14 +197,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sendAuth() {
|
async function sendAuth() {
|
||||||
try { void await authStore.loginWithCredentials(login.value, password.value) }
|
try {
|
||||||
|
void await authStore.loginWithCredentials(login.value, password.value)
|
||||||
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
await router.push({ name: 'projects' })
|
await router.push({ name: 'projects' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function forgotPwd() {
|
async function forgotPwd() {
|
||||||
sessionStorage.setItem('pendingLogin', login.value)
|
sessionStorage.setItem('pendingLogin', login.value)
|
||||||
await router.push({ name: 'recovery_password' })
|
await router.push({ name: 'recovery_password' })
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<router-view/>
|
<router-view/>
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
</q-tab-panels>
|
</q-tab-panels>
|
||||||
<template #footer>
|
<template #tabs>
|
||||||
<q-footer class="bg-grey-1 text-grey main-content">
|
<q-footer class="bg-grey-1 text-grey main-content">
|
||||||
<q-tabs
|
<q-tabs
|
||||||
style = "z-index: 1000"
|
style = "z-index: 1000"
|
||||||
|
|||||||
@@ -14,14 +14,11 @@
|
|||||||
<pn-account-block-name/>
|
<pn-account-block-name/>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</template>
|
</template>
|
||||||
|
<pn-scroll-list
|
||||||
<pn-scroll-list>
|
:hideHeader="projects.length === 0 && archiveProjects.length === 0"
|
||||||
<template
|
|
||||||
#card-body-header
|
|
||||||
v-if="projects.length!==0 || archiveProjects.length!==0"
|
|
||||||
>
|
>
|
||||||
|
<template #card-body-header>
|
||||||
<q-input
|
<q-input
|
||||||
v-if="projects.length!==0"
|
|
||||||
v-model="searchProject"
|
v-model="searchProject"
|
||||||
clearable
|
clearable
|
||||||
clear-icon="close"
|
clear-icon="close"
|
||||||
|
|||||||
@@ -1,21 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-none flex column col-grow no-scroll">
|
<div class="q-pa-none flex column col-grow no-scroll">
|
||||||
<pn-scroll-list>
|
<pn-scroll-list
|
||||||
<template #card-body-header>
|
:hideHeader="chats.length === 0"
|
||||||
<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 filled
|
|
||||||
class="col-grow"
|
|
||||||
v-if="chats.length !== 0"
|
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #card-body-header>
|
||||||
<q-icon name="mdi-magnify" />
|
<pn-action-bar
|
||||||
|
v-model="search"
|
||||||
|
placeholder="chats__search"
|
||||||
|
:show-calendar-btn="false"
|
||||||
|
:filter-active="showFiltersDialog"
|
||||||
|
:filter-badge="!checkFiltersSelect"
|
||||||
|
@open-filters="showFiltersDialog = true"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
|
||||||
|
<template #card-body-btn>
|
||||||
|
<div class="w100 flex justify-end q-px-md">
|
||||||
|
<q-btn
|
||||||
|
@click="showDialogHelp = !showDialogHelp"
|
||||||
|
flat dense no-caps
|
||||||
|
class="q-my-xs"
|
||||||
|
>
|
||||||
|
<div class="text-caption flex no-wrap items-center q-px-xs text-grey">
|
||||||
|
<span class="q-pr-xs">
|
||||||
|
{{ $t("chats__help_chat_type") }}
|
||||||
|
</span>
|
||||||
|
<q-icon name="mdi-help-circle-outline" size="xs"/>
|
||||||
|
</div>
|
||||||
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -50,12 +62,28 @@
|
|||||||
<q-item-label caption lines="2">
|
<q-item-label caption lines="2">
|
||||||
{{ item.description }}
|
{{ item.description }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label caption lines="1">
|
|
||||||
|
|
||||||
</q-item-label>
|
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
|
<div class="row no-wrap items-center">
|
||||||
|
<span class="text-caption q-pr-xs">
|
||||||
|
{{ showDate(item.bot_access_date) }}
|
||||||
|
</span>
|
||||||
|
<q-icon
|
||||||
|
:name="getChatType(item.bot_access).icon"
|
||||||
|
:color="getChatType(item.bot_access).color"
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center no-wrap text-caption">
|
<div class="flex items-center no-wrap text-caption">
|
||||||
|
<q-icon
|
||||||
|
v-if="item.restore_date"
|
||||||
|
:name="getChatType('reconnect').icon"
|
||||||
|
:color="getChatType('reconnect').color"
|
||||||
|
size="xs"
|
||||||
|
class="q-pr-sm"
|
||||||
|
/>
|
||||||
|
|
||||||
<q-icon name="mdi-account-outline"/>
|
<q-icon name="mdi-account-outline"/>
|
||||||
<span>{{ item.user_count }}</span>
|
<span>{{ item.user_count }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,41 +128,54 @@
|
|||||||
@click="showOverlay = !showOverlay"
|
@click="showOverlay = !showOverlay"
|
||||||
:disable="!tg.initData"
|
: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
|
<q-fab-action
|
||||||
v-for="item in fabMenu"
|
|
||||||
:key="item.id"
|
|
||||||
square
|
square
|
||||||
clickable
|
clickable
|
||||||
v-ripple
|
v-ripple
|
||||||
class="bg-white change-fab-action"
|
class="bg-white change-fab-action q-pa-none"
|
||||||
@click="item.func()"
|
style="cursor: default"
|
||||||
|
label-style=""
|
||||||
>
|
>
|
||||||
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<q-item class="q-pa-xs w100">
|
<q-list class="q-py-sm">
|
||||||
|
<q-item
|
||||||
|
v-for="item in fabMenu"
|
||||||
|
:key="item.id"
|
||||||
|
class="w100"
|
||||||
|
:clickable="item.type !== 'header'"
|
||||||
|
v-ripple
|
||||||
|
@click="item.type !== 'header' && item.func ? item?.func() : () => {}"
|
||||||
|
>
|
||||||
|
<template v-if="item.type === 'header'">
|
||||||
|
<div class="w100 column" :class="item.id === 4 ? 'q-pt-md' : ''">
|
||||||
|
<span
|
||||||
|
class="w100 text-center text-bold"
|
||||||
|
:class="'text-' + item.color"
|
||||||
|
style="line-height: 1rem;">
|
||||||
|
{{ $t(item.title) }}
|
||||||
|
</span>
|
||||||
|
<div class="text-caption text-center text-grey fab-action-item">
|
||||||
|
{{ $t(item.description) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
<q-item-section avatar class="items-center">
|
<q-item-section avatar class="items-center">
|
||||||
<q-avatar color="brand" rounded text-color="white" :icon="item.icon" />
|
<q-avatar :color="item.color" rounded text-color="white" :icon="item.icon" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
||||||
<q-item-section class="items-start">
|
<q-item-section class="items-start">
|
||||||
<q-item-label class="fab-action-item">
|
<q-item-label class="fab-action-item">
|
||||||
{{ $t(item.name) }}
|
{{ $t(item.title) }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label caption class="fab-action-item">
|
<q-item-label caption class="fab-action-item text-left">
|
||||||
{{ $t(item.description) }}
|
{{ $t(item.description) }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
|
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
</template>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
</q-list>
|
||||||
</template>
|
</template>
|
||||||
</q-fab-action>
|
</q-fab-action>
|
||||||
</q-fab>
|
</q-fab>
|
||||||
@@ -157,14 +198,98 @@
|
|||||||
@before-hide="onDialogBeforeHide()"
|
@before-hide="onDialogBeforeHide()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<pn-simple-scroll-dialog
|
||||||
|
v-model="showDialogHelp"
|
||||||
|
title="chat__help_title"
|
||||||
|
>
|
||||||
|
<div class="q-py-none q-px-md q-ma-none">
|
||||||
|
<q-list class="q-pt-sm">
|
||||||
|
<pn-chat-type-item
|
||||||
|
v-for="item in [0, 1, 2, 9, 10] as const"
|
||||||
|
:key="item"
|
||||||
|
:type="item"
|
||||||
|
class="q-px-none"
|
||||||
|
/>
|
||||||
|
</q-list>
|
||||||
|
<q-separator class="q-mt-md"/>
|
||||||
|
<div class="text-caption q-pt-md">
|
||||||
|
{{ $t('chat_type__dialog_func_title') }}
|
||||||
|
<ol class="q-px-md q-my-xs">
|
||||||
|
<li>{{ $t('chat_type__dialog_func_point1') }}</li>
|
||||||
|
<li>{{ $t('chat_type__dialog_func_point2') }}</li>
|
||||||
|
<li>{{ $t('chat_type__dialog_func_point3') }}</li>
|
||||||
|
</ol>
|
||||||
|
<div class="q-pt-md">
|
||||||
|
{{ $t('chat_type__dialog_administrator_rights')}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</pn-simple-scroll-dialog>
|
||||||
|
|
||||||
|
<!-- filter dialog -->
|
||||||
|
<pn-bottom-sheet-dialog
|
||||||
|
title="chats__filters"
|
||||||
|
v-model="showFiltersDialog"
|
||||||
|
>
|
||||||
|
<template #btnSlot>
|
||||||
|
<div>
|
||||||
|
<q-btn
|
||||||
|
v-if="!checkFiltersSelect"
|
||||||
|
@click="resetFilters"
|
||||||
|
flat
|
||||||
|
no-caps
|
||||||
|
dense
|
||||||
|
color="grey-6"
|
||||||
|
>
|
||||||
|
{{ $t('chats__filters_reset')}}
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<q-btn
|
||||||
|
rounded
|
||||||
|
class="w100"
|
||||||
|
color="primary"
|
||||||
|
@click="showFiltersDialog = false"
|
||||||
|
>
|
||||||
|
{{$t('chats__filters_continue')}}
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="q-pl-sm text-bold text-caption">
|
||||||
|
{{ $t('chats__filters_type') }}
|
||||||
|
</div>
|
||||||
|
<div class="flex column">
|
||||||
|
<div
|
||||||
|
v-for="(item,idx) in chatTypeOptions"
|
||||||
|
:key="idx"
|
||||||
|
>
|
||||||
|
<q-checkbox
|
||||||
|
v-model="filters.chatType"
|
||||||
|
:val="item.value"
|
||||||
|
>
|
||||||
|
{{ $t(item.label) }}
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</pn-bottom-sheet-dialog>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onActivated, onDeactivated, onBeforeUnmount, inject } from 'vue'
|
import { ref, computed, onActivated, onDeactivated, onBeforeUnmount, inject } from 'vue'
|
||||||
import { useChatsStore } from 'stores/chats'
|
import { useChatsStore } from 'stores/chats'
|
||||||
import type { WebApp } from '@twa-dev/types'
|
import type { WebApp } from '@twa-dev/types'
|
||||||
|
import type { Chat, ChatType } from 'types/Chat'
|
||||||
import { useI18n } from "vue-i18n"
|
import { useI18n } from "vue-i18n"
|
||||||
|
import { date } from 'quasar'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import pnChatTypeItem from 'components/pnChatTypeItem.vue'
|
||||||
|
import pnSimpleScrollDialog from 'components/pnSimpleScrollDialog.vue'
|
||||||
|
import pnActionBar from 'components/pnActionBar.vue'
|
||||||
|
import pnBottomSheetDialog from 'components/pnBottomSheetDialog.vue'
|
||||||
|
import { getChatType } from 'utils/chats'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -178,6 +303,7 @@
|
|||||||
const deleteChatId = ref<number | undefined>(undefined)
|
const deleteChatId = ref<number | undefined>(undefined)
|
||||||
const currentSlideEvent = ref<SlideEvent | null>(null)
|
const currentSlideEvent = ref<SlideEvent | null>(null)
|
||||||
const closedByUserAction = ref(false)
|
const closedByUserAction = ref(false)
|
||||||
|
const showDialogHelp = ref(false)
|
||||||
const tg = inject('tg') as WebApp
|
const tg = inject('tg') as WebApp
|
||||||
|
|
||||||
const fabState = ref(false)
|
const fabState = ref(false)
|
||||||
@@ -190,19 +316,30 @@
|
|||||||
const chatsInit = computed(() => chatsStore.isInit)
|
const chatsInit = computed(() => chatsStore.isInit)
|
||||||
|
|
||||||
const fabMenu = [
|
const fabMenu = [
|
||||||
{id: 1, icon: 'mdi-chat-plus-outline', name: 'chats__attach_chat', description: 'chats__attach_chat_description', func: attachChat},
|
{ id: 1, type: 'header', title: 'chats__attach_chat_private_title', description: 'chats__attach_chat_private_title_description', color: 'info' },
|
||||||
{id: 2, icon: 'mdi-share-outline', name: 'chats__send_chat', description: 'chats__send_chat_description', func: sendChat},
|
{ id: 2, icon: 'mdi-chat-plus-outline', title: 'chats__attach_private_chat', description: 'chats__attach_private_chat_description', func: attachPrivateChat, color: 'info' },
|
||||||
|
{ id: 3, icon: 'mdi-share-outline', title: 'chats__send_private_chat', description: 'chats__send_private_chat_description', func: sendPrivateChat, color: 'info'},
|
||||||
|
{ id: 4, type: 'header', title: 'chats__attach_chat_title', description: 'chats__attach_chat_title_description', color: 'primary' },
|
||||||
|
{ id: 5, icon: 'mdi-chat-plus-outline', title: 'chats__attach_chat', description: 'chats__attach_chat_description', func: attachChat, color: 'primary' },
|
||||||
|
{ id: 6, icon: 'mdi-share-outline', title: 'chats__send_chat', description: 'chats__send_chat_description', func: sendChat, color: 'primary' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const displayChats = computed(() => {
|
const displayChats = computed(() => {
|
||||||
if (!search.value || !(search.value && search.value.trim())) return chats.value
|
return chats.value
|
||||||
|
.filter(searchChats)
|
||||||
|
.filter(chatsType)
|
||||||
|
|
||||||
|
function searchChats (el: Chat) {
|
||||||
|
if (!search.value || !(search.value && search.value.trim())) return true
|
||||||
const searchValue = search.value.trim().toLowerCase()
|
const searchValue = search.value.trim().toLowerCase()
|
||||||
const arrOut = chats.value
|
return el.name.toLowerCase().includes(searchValue) ||
|
||||||
.filter(el =>
|
|
||||||
el.name.toLowerCase().includes(searchValue) ||
|
|
||||||
el.description && el.description.toLowerCase().includes(searchValue)
|
el.description && el.description.toLowerCase().includes(searchValue)
|
||||||
)
|
}
|
||||||
return arrOut
|
|
||||||
|
function chatsType (el: Chat) {
|
||||||
|
if (filters.value.chatType.length === 0) return true
|
||||||
|
return filters.value.chatType.includes(el.bot_access)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleSlide (event: SlideEvent, id: number) {
|
function handleSlide (event: SlideEvent, id: number) {
|
||||||
@@ -235,23 +372,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const botName = 'ready_or_not_2025_bot'
|
const botName = 'ready_or_not_2025_bot'
|
||||||
const urlAdmin = 'https://t.me/' + botName + '?startgroup='
|
const url = 'https://t.me/' + botName + '?startgroup='
|
||||||
const urlAdminPermission='&admin=' +
|
const urlAdminPermission='&admin=' +
|
||||||
'post_messages+' +
|
|
||||||
'edit_messages+' +
|
|
||||||
'delete_messages+' +
|
|
||||||
'pin_messages+' +
|
'pin_messages+' +
|
||||||
'restrict_members+' +
|
'restrict_members'
|
||||||
'invite_users'
|
|
||||||
|
|
||||||
async function attachChat () {
|
async function attachChat () {
|
||||||
const key = await chatsStore.getKey()
|
const key = await chatsStore.getKey()
|
||||||
tg.openTelegramLink(urlAdmin + key + urlAdminPermission)
|
tg.openTelegramLink(url + key + urlAdminPermission)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendChat () {
|
async function sendChat () {
|
||||||
const key = await chatsStore.getKey()
|
const key = await chatsStore.getKey()
|
||||||
const message = urlAdmin + key + urlAdminPermission
|
const message = url + key + urlAdminPermission
|
||||||
|
const tgShareUrl = 'https://t.me/share/url?url=' +
|
||||||
|
encodeURIComponent( t('chats__send_chat_title')) +
|
||||||
|
'&text=' + `${encodeURIComponent(message)}`
|
||||||
|
tg.openTelegramLink(tgShareUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function attachPrivateChat () {
|
||||||
|
const key = await chatsStore.getKey()
|
||||||
|
tg.openTelegramLink(url + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendPrivateChat () {
|
||||||
|
const key = await chatsStore.getKey()
|
||||||
|
const message = url + key
|
||||||
const tgShareUrl = 'https://t.me/share/url?url=' +
|
const tgShareUrl = 'https://t.me/share/url?url=' +
|
||||||
encodeURIComponent( t('chats__send_chat_title')) +
|
encodeURIComponent( t('chats__send_chat_title')) +
|
||||||
'&text=' + `${encodeURIComponent(message)}`
|
'&text=' + `${encodeURIComponent(message)}`
|
||||||
@@ -284,6 +431,40 @@
|
|||||||
if (timerId.value) clearTimeout(timerId.value)
|
if (timerId.value) clearTimeout(timerId.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// filter chat
|
||||||
|
const showFiltersDialog = ref(false)
|
||||||
|
interface Filters {
|
||||||
|
chatType: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultFilters = {
|
||||||
|
chatType: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const filters = ref<Filters>({ ...defaultFilters })
|
||||||
|
|
||||||
|
const checkFiltersSelect = computed(() => (
|
||||||
|
Object.values(filters.value).every(el => el.length === 0)
|
||||||
|
))
|
||||||
|
|
||||||
|
function resetFilters() {
|
||||||
|
(Object.keys(filters.value) as (keyof Filters)[]).forEach(key => filters.value[key] = [])
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatTypeOptions = computed(() => {
|
||||||
|
return [2, 1, 0, 9].map(el => ({
|
||||||
|
id: el,
|
||||||
|
value: el,
|
||||||
|
label: getChatType(el as Partial<ChatType>).label
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
function showDate (d: number) {
|
||||||
|
return new Date(d * 1000).getFullYear() === new Date(Date.now()).getFullYear()
|
||||||
|
? date.formatDate(d * 1000, 'DD MMM')
|
||||||
|
: date.formatDate(d * 1000, 'MMM YY')
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -292,13 +473,21 @@
|
|||||||
max-height: none;
|
max-height: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.change-fab-action :deep(.q-focus-helper) {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-item :deep(.q-focus-helper) {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.change-fab-action {
|
.change-fab-action {
|
||||||
width: calc(min(100vw, var(--body-width)) - 48px) !important;
|
width: calc(min(100vw, var(--body-width)) - 48px) !important;
|
||||||
|
border-radius: var(--top-radius)
|
||||||
}
|
}
|
||||||
|
|
||||||
.fab-action-item {
|
.fab-action-item {
|
||||||
text-wrap: auto !important;
|
text-wrap: auto !important;
|
||||||
text-align: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fix mini border after slide */
|
/* fix mini border after slide */
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-none flex column col-grow no-scroll">
|
<div class="q-pa-none flex column col-grow no-scroll">
|
||||||
<pn-scroll-list>
|
<pn-scroll-list
|
||||||
|
hideHeader
|
||||||
|
>
|
||||||
|
<template #card-body-btn>
|
||||||
<div class="flex items-center justify-end q-pa-sm w100" v-if="companies.length !== 0">
|
<div class="flex items-center justify-end q-pa-sm w100" v-if="companies.length !== 0">
|
||||||
<q-btn
|
<q-btn
|
||||||
:color="companies.length <= 2 ? 'grey-6' : 'primary'"
|
:color="companies.length <= 2 ? 'grey-6' : 'primary'"
|
||||||
@@ -22,6 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<q-list separator v-if="companies.length !== 0">
|
<q-list separator v-if="companies.length !== 0">
|
||||||
<q-slide-item
|
<q-slide-item
|
||||||
@@ -55,7 +59,15 @@
|
|||||||
{{ $t('companies__my_company') }}
|
{{ $t('companies__my_company') }}
|
||||||
</div>
|
</div>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label lines="1" class="text-bold">{{ item.name }}</q-item-label>
|
<q-item-label lines="1" class="text-bold">
|
||||||
|
{{ item.name }}
|
||||||
|
<q-icon
|
||||||
|
v-if="companiesStore.checkCompanyMasked(item.id)"
|
||||||
|
name="mdi-domino-mask"
|
||||||
|
color="grey"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</q-item-label>
|
||||||
<q-item-label
|
<q-item-label
|
||||||
caption lines="2"
|
caption lines="2"
|
||||||
style="max-width: -webkit-fill-available; white-space: pre-line"
|
style="max-width: -webkit-fill-available; white-space: pre-line"
|
||||||
@@ -63,20 +75,13 @@
|
|||||||
{{ item.description }}
|
{{ item.description }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side top>
|
|
||||||
|
<q-item-section side>
|
||||||
<div class="flex items-end column">
|
<div class="flex items-end column">
|
||||||
<span class="text-caption flex items-center">
|
<span class="text-caption flex items-center">
|
||||||
<q-icon name="mdi-account-outline" color="grey" />
|
<q-icon name="mdi-account-outline" color="grey" />
|
||||||
<span>{{ getQtyUsers(item.id) }}</span>
|
<span>{{ getQtyUsers(item.id) }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<q-icon
|
|
||||||
v-if="companiesStore.checkCompanyMasked(item.id)"
|
|
||||||
name="mdi-domino-mask"
|
|
||||||
color="grey"
|
|
||||||
size="xs"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="flex items-center row text-caption">
|
<div class="flex items-center row text-caption">
|
||||||
<q-icon v-if="item.site" name="mdi-web"/>
|
<q-icon v-if="item.site" name="mdi-web"/>
|
||||||
<q-icon v-if="item.address" name="mdi-map-marker-outline"/>
|
<q-icon v-if="item.address" name="mdi-map-marker-outline"/>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
<div class="flex column text-white fit q-pl-sm">
|
<div class="flex column text-white fit q-pl-sm">
|
||||||
<div
|
<div
|
||||||
class="text-h6 q-pl-sm text-field"
|
class="text-h6 q-pl-sm text-field"
|
||||||
|
style="line-height: 1.45rem;"
|
||||||
>
|
>
|
||||||
{{ project.name }}
|
{{ project.name }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-none flex column col-grow no-scroll">
|
<div class="q-pa-none flex column col-grow no-scroll">
|
||||||
<pn-scroll-list>
|
<pn-scroll-list
|
||||||
|
:hideHeader="users.length === 0"
|
||||||
|
>
|
||||||
<template #card-body-header>
|
<template #card-body-header>
|
||||||
<div class="flex row q-ma-md justify-between" v-if="users.length !== 0">
|
<div class="flex row q-ma-md justify-between">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="search"
|
v-model="search"
|
||||||
clearable
|
clearable
|
||||||
@@ -42,9 +44,9 @@
|
|||||||
/>
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label lines="1" v-if="!item.is_terms_accepted">
|
<q-item-label lines="1" v-if="getUserStatus(item) && getUserStatus(item)?.status">
|
||||||
<q-badge color="secondary">
|
<q-badge :color="getUserStatus(item)?.color">
|
||||||
{{ $t('user_block__user_pending') }}
|
{{ $t(getUserStatus(item)?.text ?? '') }}
|
||||||
</q-badge>
|
</q-badge>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label lines="1" class="text-bold" v-if="item.section1">
|
<q-item-label lines="1" class="text-bold" v-if="item.section1">
|
||||||
@@ -244,6 +246,7 @@
|
|||||||
import { useUsersStore } from 'stores/users'
|
import { useUsersStore } from 'stores/users'
|
||||||
import { useCompaniesStore } from 'stores/companies'
|
import { useCompaniesStore } from 'stores/companies'
|
||||||
import { useUserSection } from 'composables/useUserSection'
|
import { useUserSection } from 'composables/useUserSection'
|
||||||
|
import { getUserStatus } from 'utils/users'
|
||||||
|
|
||||||
defineOptions({ inheritAttrs: false })
|
defineOptions({ inheritAttrs: false })
|
||||||
|
|
||||||
|
|||||||
104
src/stores/SSE.ts
Normal file
104
src/stores/SSE.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { api } from 'boot/axios'
|
||||||
|
import { useProjectsStore } from 'stores/projects'
|
||||||
|
import { useChatsStore } from 'stores/chats'
|
||||||
|
import { useUsersStore } from 'stores/users'
|
||||||
|
|
||||||
|
export const useSSEStore = defineStore('sse', () => {
|
||||||
|
const eventSource = ref<EventSource | null>(null)
|
||||||
|
const isConnected = ref(false)
|
||||||
|
const reconnectAttempts = ref(0)
|
||||||
|
const maxReconnectAttempts = 5
|
||||||
|
|
||||||
|
const projectsStore = useProjectsStore()
|
||||||
|
const chatsStore = useChatsStore()
|
||||||
|
const usersStore = useUsersStore()
|
||||||
|
|
||||||
|
const currentProjectId = computed(() => projectsStore.currentProjectId)
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
try {
|
||||||
|
if (eventSource.value) eventSource.value.close()
|
||||||
|
|
||||||
|
const url = api.defaults.baseURL + '/events'
|
||||||
|
eventSource.value = new EventSource(url)
|
||||||
|
|
||||||
|
setupEventListeners()
|
||||||
|
|
||||||
|
eventSource.value.onopen = () => {
|
||||||
|
isConnected.value = true
|
||||||
|
reconnectAttempts.value = 0
|
||||||
|
console.log('SSE connected')
|
||||||
|
}
|
||||||
|
|
||||||
|
eventSource.value.onerror = (error) => {
|
||||||
|
console.error('SSE error:', error)
|
||||||
|
isConnected.value = false
|
||||||
|
|
||||||
|
if (reconnectAttempts.value < maxReconnectAttempts) {
|
||||||
|
setTimeout(() => {
|
||||||
|
reconnectAttempts.value++
|
||||||
|
connect()
|
||||||
|
}, 3000 * reconnectAttempts.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to connect SSE:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const disconnect = () => {
|
||||||
|
if (eventSource.value) {
|
||||||
|
eventSource.value.close()
|
||||||
|
eventSource.value = null
|
||||||
|
}
|
||||||
|
isConnected.value = false
|
||||||
|
reconnectAttempts.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupEventListeners = () => {
|
||||||
|
if (!eventSource.value) return
|
||||||
|
|
||||||
|
eventSource.value.addEventListener('chat-attach', (event) => {
|
||||||
|
const data = JSON.parse(event.data)
|
||||||
|
if (data.project_id === currentProjectId.value) {
|
||||||
|
chatsStore.init().catch(() => {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.value.addEventListener('chat-delete', (event) => {
|
||||||
|
const data = JSON.parse(event.data)
|
||||||
|
if (data.project_id === currentProjectId.value) {
|
||||||
|
chatsStore.init().catch(() => {})
|
||||||
|
usersStore.init().catch(() => {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.value.addEventListener('chat-detach', (event) => {
|
||||||
|
const data = JSON.parse(event.data)
|
||||||
|
if (data.project_id === currentProjectId.value) {
|
||||||
|
chatsStore.detach(data.id)
|
||||||
|
usersStore.init().catch(() => {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eventSource.value.addEventListener('chat-update', (event) => {
|
||||||
|
const data = JSON.parse(event.data)
|
||||||
|
if (data.project_id === currentProjectId.value) {
|
||||||
|
chatsStore.init().catch(() => {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventSourceComputed = computed(() => eventSource.value)
|
||||||
|
const isConnectedComputed = computed(() => isConnected.value)
|
||||||
|
|
||||||
|
return {
|
||||||
|
eventSource: eventSourceComputed,
|
||||||
|
isConnected: isConnectedComputed,
|
||||||
|
connect,
|
||||||
|
disconnect
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -42,32 +42,6 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
try {
|
try {
|
||||||
const { data } = await api.get('/customer/profile', { suppressNotify: true })
|
const { data } = await api.get('/customer/profile', { suppressNotify: true })
|
||||||
customer.value = data.data
|
customer.value = data.data
|
||||||
const socket = new WebSocket("wss://946gp81j-9000.euw.devtunnels.ms/api/admin")
|
|
||||||
console.log(socket)
|
|
||||||
socket.onopen = () => console.log("Connection ws create.")
|
|
||||||
|
|
||||||
socket.onclose = (event) => {
|
|
||||||
if (event.wasClean) console.log('Connection ws close.')
|
|
||||||
else console.log('Connection ws failure!')
|
|
||||||
console.log('Code: ' + event.code + ', reason : ' + event.reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
|
||||||
const eventData = JSON.parse(event.data)
|
|
||||||
console.log(eventData, eventData.event === 'chat-attached')
|
|
||||||
if (eventData.event === 'chat-attached') {
|
|
||||||
console.log(eventData)
|
|
||||||
wsEvent.value = { needUpdateStores: ['users', 'chats' ] }
|
|
||||||
}
|
|
||||||
/* if (wsEvent.value) {
|
|
||||||
wsEvent.value.needUpdateStores = []
|
|
||||||
wsEvent.value = JSON.parse(event.data)
|
|
||||||
if (wsEvent.value?.entity === 'chat') wsEvent.value.needUpdateStores = ['users']
|
|
||||||
} */
|
|
||||||
|
|
||||||
socket.onerror = (event) => console.error("Ошибка ", event)
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isAuth.value) console.error(error)
|
if (isAuth.value) console.error(error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { api } from 'boot/axios'
|
import { api } from 'boot/axios'
|
||||||
import type { Chat } from 'types/Chat'
|
import type { Chat } from 'types/Chat'
|
||||||
import { useProjectsStore } from 'stores/projects'
|
import { useProjectsStore } from 'stores/projects'
|
||||||
import { useAuthStore } from 'stores/auth'
|
import { useUsersStore } from 'stores/users'
|
||||||
|
|
||||||
export const useChatsStore = defineStore('chats', () => {
|
export const useChatsStore = defineStore('chats', () => {
|
||||||
const projectsStore = useProjectsStore()
|
const projectsStore = useProjectsStore()
|
||||||
@@ -15,8 +15,7 @@ export const useChatsStore = defineStore('chats', () => {
|
|||||||
async function init () {
|
async function init () {
|
||||||
reset()
|
reset()
|
||||||
const { data } = await api.get('/project/' + currentProjectId.value + '/chat')
|
const { data } = await api.get('/project/' + currentProjectId.value + '/chat')
|
||||||
const chatsAPI = data.data
|
chats.value.push(...data.data)
|
||||||
chats.value.push(...chatsAPI)
|
|
||||||
isInit.value = true
|
isInit.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,30 +26,33 @@ export const useChatsStore = defineStore('chats', () => {
|
|||||||
|
|
||||||
async function unlink (chatId: number) {
|
async function unlink (chatId: number) {
|
||||||
const { data } = await api.delete('/project/' + currentProjectId.value + '/chat/' + chatId)
|
const { data } = await api.delete('/project/' + currentProjectId.value + '/chat/' + chatId)
|
||||||
const chatAPIid = data.data.id
|
chats.value = chats.value.filter(item => item.id !== data.data.id)
|
||||||
const idx = chats.value.findIndex(item => item.id === chatAPIid)
|
const usersStore = useUsersStore()
|
||||||
chats.value.splice(idx, 1)
|
await usersStore.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getKey () {
|
async function getKey () {
|
||||||
const { data } = await api.get('/project/' + currentProjectId.value + '/token')
|
const { data } = await api.get('/project/' + currentProjectId.value + '/token')
|
||||||
const key = <string>data.data
|
return <string>data.data
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getChatUsers (chatId: number) {
|
|
||||||
await api.get('/project/' + currentProjectId.value + '/chat/' + chatId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function chatById (id: number) {
|
function chatById (id: number) {
|
||||||
return chats.value.find(el => el.id === id)
|
return chats.value.find(el => el.id === id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
// attach, update and detach for use in SSE-store
|
||||||
watch(() => authStore.wsEvent, async (event) => {
|
function attach (chat: Chat) {
|
||||||
if (!event || !event.needUpdateStores.includes('chats')) return
|
chats.value.push(chat)
|
||||||
await init()
|
}
|
||||||
}, { deep: true })
|
|
||||||
|
function update (chat: Chat) {
|
||||||
|
const idx = chats.value.findIndex(item => item.id === chat.id)
|
||||||
|
if (chats.value[idx]) Object.assign(chats.value[idx], chat)
|
||||||
|
}
|
||||||
|
|
||||||
|
function detach (chatId: number) {
|
||||||
|
chats.value = chats.value.filter(item => item.id !== chatId)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chats,
|
chats,
|
||||||
@@ -60,6 +62,8 @@ export const useChatsStore = defineStore('chats', () => {
|
|||||||
unlink,
|
unlink,
|
||||||
getKey,
|
getKey,
|
||||||
chatById,
|
chatById,
|
||||||
getChatUsers
|
attach,
|
||||||
|
update,
|
||||||
|
detach
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { defineStore } from 'pinia'
|
|||||||
import { api } from 'boot/axios'
|
import { api } from 'boot/axios'
|
||||||
import type { Company, CompanyParams, CompanyMask } from 'types/Company'
|
import type { Company, CompanyParams, CompanyMask } from 'types/Company'
|
||||||
import { useProjectsStore } from 'stores/projects'
|
import { useProjectsStore } from 'stores/projects'
|
||||||
|
import { useUsersStore } from 'stores/users'
|
||||||
|
|
||||||
export const useCompaniesStore = defineStore('companies', () => {
|
export const useCompaniesStore = defineStore('companies', () => {
|
||||||
const projectsStore = useProjectsStore()
|
const projectsStore = useProjectsStore()
|
||||||
@@ -44,9 +45,9 @@ export const useCompaniesStore = defineStore('companies', () => {
|
|||||||
|
|
||||||
async function remove (companyId: number) {
|
async function remove (companyId: number) {
|
||||||
const { data } = await api.delete('/project/' + currentProjectId.value + '/company/' + companyId)
|
const { data } = await api.delete('/project/' + currentProjectId.value + '/company/' + companyId)
|
||||||
const companyAPIid = data.data.id
|
companies.value = companies.value.filter(item => item.id !== data.data.id)
|
||||||
const idx = companies.value.findIndex(item => item.id === companyAPIid)
|
const usersStore = useUsersStore()
|
||||||
companies.value.splice(idx, 1)
|
await usersStore.init()
|
||||||
await getCompanyMasked()
|
await getCompanyMasked()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,8 +57,7 @@ export const useCompaniesStore = defineStore('companies', () => {
|
|||||||
|
|
||||||
async function getCompanyMasked () {
|
async function getCompanyMasked () {
|
||||||
const { data } = await api.get('/project/' + currentProjectId.value + '/company/mapping')
|
const { data } = await api.get('/project/' + currentProjectId.value + '/company/mapping')
|
||||||
const companiesMaskAPI = data.data // arr [company_id, company_list[]]
|
companiesMask.value = data.data // arr [company_id, company_list[]]
|
||||||
companiesMask.value = companiesMaskAPI
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkCompanyMasked (id: number) {
|
function checkCompanyMasked (id: number) {
|
||||||
@@ -66,8 +66,7 @@ export const useCompaniesStore = defineStore('companies', () => {
|
|||||||
|
|
||||||
async function updateMask (mask: CompanyMask[]) {
|
async function updateMask (mask: CompanyMask[]) {
|
||||||
const { data } = await api.put('/project/' + currentProjectId.value + '/company/mapping', mask)
|
const { data } = await api.put('/project/' + currentProjectId.value + '/company/mapping', mask)
|
||||||
const maskAPI = data.data
|
companiesMask.value = data.data
|
||||||
companiesMask.value = maskAPI
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCompanies = computed(() => companies.value)
|
const getCompanies = computed(() => companies.value)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, watch, computed, inject } from 'vue'
|
import { ref, computed, inject } from 'vue'
|
||||||
import { api } from 'boot/axios'
|
import { api } from 'boot/axios'
|
||||||
import { useAuthStore } from 'stores/auth'
|
import { useAuthStore } from 'stores/auth'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import type { WebApp } from '@twa-dev/types'
|
import type { WebApp } from '@twa-dev/types'
|
||||||
|
|
||||||
|
|
||||||
interface AppSettings {
|
interface AppSettings {
|
||||||
fontSize: number
|
fontSize: number
|
||||||
locale: string
|
locale: string
|
||||||
@@ -79,13 +78,22 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
const init = async () => {
|
const init = async () => {
|
||||||
if (authStore.isAuth) {
|
if (authStore.isAuth) {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.get('/customer/settings')
|
const response = await api.get<{ data: AppSettings }>('/customer/settings')
|
||||||
settings.value = {
|
let serverData = response.data.data
|
||||||
fontSize: data.data.fontSize || defaultSettings.fontSize,
|
const updateData: Partial<AppSettings> = {}
|
||||||
locale: data.data.locale || detectLocale(),
|
|
||||||
timeZoneBot: data.data.timeZone || defaultSettings.timeZoneBot,
|
if (!serverData.fontSize) updateData.fontSize = defaultSettings.fontSize
|
||||||
localeBot: data.data.localeBot || detectLocale()
|
if (!serverData.locale) updateData.locale = detectLocale()
|
||||||
|
if (!serverData.timeZoneBot) updateData.timeZoneBot = defaultSettings.timeZoneBot
|
||||||
|
if (!serverData.localeBot) updateData.localeBot = detectLocale()
|
||||||
|
|
||||||
|
if (Object.keys(updateData).length > 0) {
|
||||||
|
await api.put('/customer/settings', updateData)
|
||||||
|
const response = await api.get<{ data: AppSettings }>('/customer/settings')
|
||||||
|
serverData = response.data.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings.value = serverData
|
||||||
} catch {
|
} catch {
|
||||||
settings.value.locale = detectLocale()
|
settings.value.locale = detectLocale()
|
||||||
}
|
}
|
||||||
@@ -111,10 +119,6 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
await saveSettings()
|
await saveSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => authStore.isAuth, (newVal) => {
|
|
||||||
if (newVal !== undefined) void init()
|
|
||||||
}, { immediate: true })
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
settings,
|
settings,
|
||||||
supportLocale,
|
supportLocale,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { api } from 'boot/axios'
|
import { api } from 'boot/axios'
|
||||||
import type { User, UserParams } from 'types/User'
|
|
||||||
import { useProjectsStore } from 'stores/projects'
|
import { useProjectsStore } from 'stores/projects'
|
||||||
import { useAuthStore } from 'stores/auth'
|
import type { User, UserParams } from 'types/User'
|
||||||
|
|
||||||
export const useUsersStore = defineStore('users', () => {
|
export const useUsersStore = defineStore('users', () => {
|
||||||
const projectsStore = useProjectsStore()
|
const projectsStore = useProjectsStore()
|
||||||
@@ -14,7 +13,7 @@ export const useUsersStore = defineStore('users', () => {
|
|||||||
|
|
||||||
async function init () {
|
async function init () {
|
||||||
reset()
|
reset()
|
||||||
const { data } = await api.get('/project/' + currentProjectId.value + '/user')
|
const { data } = await api.get(`/project/${currentProjectId.value}/user`)
|
||||||
const usersAPI = data.data
|
const usersAPI = data.data
|
||||||
users.value.push(...usersAPI)
|
users.value.push(...usersAPI)
|
||||||
isInit.value = true
|
isInit.value = true
|
||||||
@@ -26,24 +25,26 @@ export const useUsersStore = defineStore('users', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function update (userId: number, userData: UserParams) {
|
async function update (userId: number, userData: UserParams) {
|
||||||
const { data } = await api.put('/project/' + currentProjectId.value + '/user/' + userId, userData)
|
const { data } = await api.put(`/project/${currentProjectId.value}/user/${userId}`, userData)
|
||||||
const userAPI = data.data
|
const userAPI = data.data
|
||||||
const idx = users.value.findIndex(item => item.id === userAPI.id)
|
const idx = users.value.findIndex(item => item.id === userAPI.id)
|
||||||
if (users.value[idx]) Object.assign(users.value[idx], userAPI)
|
if (users.value[idx]) Object.assign(users.value[idx], userAPI)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function blockUser (userId: number) {
|
async function blockUser (userId: number) {
|
||||||
const { data } = await api.put('/project/' + currentProjectId.value + '/user/' + userId, { is_blocked: true })
|
const { data } = await api.post(`/project/${currentProjectId.value}/user/${userId}/block`)
|
||||||
const userAPI = data.data
|
if (data.success) {
|
||||||
const idx = users.value.findIndex(item => item.id === userAPI.id)
|
const idx = users.value.findIndex(item => item.id === userId)
|
||||||
if (users.value[idx]) Object.assign(users.value[idx], userAPI)
|
if (users.value[idx]) Object.assign(users.value[idx], { is_blocked: true })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unblockUser (userId: number) {
|
async function unblockUser (userId: number) {
|
||||||
const { data } = await api.put('/project/' + currentProjectId.value + '/user/' + userId, { is_blocked: false })
|
const { data } = await api.post(`/project/${currentProjectId.value}/user/${userId}/unblock`)
|
||||||
const userAPI = data.data
|
if (data.success) {
|
||||||
const idx = users.value.findIndex(item => item.id === userAPI.id)
|
const idx = users.value.findIndex(item => item.id === userId)
|
||||||
if (users.value[idx]) Object.assign(users.value[idx], userAPI)
|
if (users.value[idx]) Object.assign(users.value[idx], { is_blocked: false })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function userById (id: number) {
|
function userById (id: number) {
|
||||||
@@ -58,12 +59,6 @@ export const useUsersStore = defineStore('users', () => {
|
|||||||
|| '---'
|
|| '---'
|
||||||
}
|
}
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
watch(() => authStore.wsEvent, async (event) => {
|
|
||||||
if (!event || !event.needUpdateStores.includes('users')) return
|
|
||||||
await init()
|
|
||||||
}, { deep: true })
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
users,
|
users,
|
||||||
isInit,
|
isInit,
|
||||||
|
|||||||
@@ -11,10 +11,23 @@ interface Chat {
|
|||||||
description: string | null
|
description: string | null
|
||||||
logo: string | null
|
logo: string | null
|
||||||
owner_id?: number
|
owner_id?: number
|
||||||
|
bot_access: 0 | 1 | 2
|
||||||
|
bot_access_date: number
|
||||||
|
bot_rights: 1 | 2 | 3
|
||||||
|
restore_date: null | number
|
||||||
users: number []
|
users: number []
|
||||||
|
last_pts?: number
|
||||||
[key: string]: number | string | boolean | null | number[]
|
[key: string]: number | string | boolean | null | number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChatType =
|
||||||
|
0 | 'detach' |
|
||||||
|
1 | 'notify' |
|
||||||
|
2 | 'full' |
|
||||||
|
9 | 'delete' |
|
||||||
|
10 | 'reconnect'
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
Chat
|
Chat,
|
||||||
|
ChatType
|
||||||
}
|
}
|
||||||
|
|||||||
50
src/utils/chats.ts
Normal file
50
src/utils/chats.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import type { ChatType } from "types/Chat"
|
||||||
|
|
||||||
|
function getChatType (type: ChatType) {
|
||||||
|
switch (type) {
|
||||||
|
default:
|
||||||
|
case 0:
|
||||||
|
case 'detach': return {
|
||||||
|
icon: 'mdi-link-off',
|
||||||
|
color: 'negative',
|
||||||
|
label: 'chat_type__detach_label',
|
||||||
|
description: 'chat_type__detach_description'
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
case 'notify': return {
|
||||||
|
icon: 'mdi-bell-outline',
|
||||||
|
color: 'info',
|
||||||
|
label: 'chat_type__notify_label',
|
||||||
|
description: 'chat_type__notify_description'
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
case 'full': return {
|
||||||
|
icon: 'mdi-star-shooting-outline',
|
||||||
|
color: 'primary',
|
||||||
|
label: 'chat_type__full_label',
|
||||||
|
description: 'chat_type__full_description'
|
||||||
|
}
|
||||||
|
|
||||||
|
case 9:
|
||||||
|
case 'delete': return {
|
||||||
|
icon: 'mdi-forum-remove-outline',
|
||||||
|
color: 'negative',
|
||||||
|
label: 'chat_type__delete_label',
|
||||||
|
description: 'chat_type__delete_description'
|
||||||
|
}
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
case 'reconnect': return {
|
||||||
|
icon: 'mdi-repeat-variant',
|
||||||
|
color: 'negative',
|
||||||
|
label: 'chat_type__reconnect_label',
|
||||||
|
description: 'chat_type__reconnect_description'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getChatType
|
||||||
|
}
|
||||||
@@ -9,7 +9,12 @@ const removeNullKeys = <T extends object>(obj: T): Partial<T> =>
|
|||||||
Object.entries(obj).filter(([, v]) => v !== null)
|
Object.entries(obj).filter(([, v]) => v !== null)
|
||||||
) as Partial<T>
|
) as Partial<T>
|
||||||
|
|
||||||
|
const getFieldValue = (mod: string, props: undefined | null | string ) => {
|
||||||
|
return mod !== '' ? mod : props === null || props === undefined ? null : ''
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
parseIntString,
|
parseIntString,
|
||||||
removeNullKeys
|
removeNullKeys,
|
||||||
|
getFieldValue
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/utils/users.ts
Normal file
12
src/utils/users.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { User } from 'types/User'
|
||||||
|
|
||||||
|
function getUserStatus (user: User) {
|
||||||
|
if (!user) return null
|
||||||
|
if (user.is_blocked) return { status: 'blocked', text: 'user_status__user_blocked', color: 'negative' }
|
||||||
|
if (user.is_leave) return { status: 'leave', text: 'user_status__user_leave', color: 'primary' }
|
||||||
|
if (!user.is_terms_accepted) return { status: 'pending', text: 'user_status__user_pending', color: 'secondary' }
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getUserStatus
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user