fix_error
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-08-14 19:21:21 +03:00
parent ab94ad69a5
commit 04ea1f83c6
39 changed files with 2326 additions and 451 deletions

View File

@@ -40,12 +40,26 @@
</q-list>
</pn-scroll-list>
</pn-page-card>
<pn-small-dialog
v-model="showStopUsingDialog"
icon="mdi-close-box-outline"
color="negative"
title="account__stop_using_dialog_title"
message1="account__stop_using_dialog_message1"
message2="account__stop_using_dialog_message2"
mainBtnLabel="account__stop_using_dialog_btn_ok"
@clickMainBtn="onConfirmStopUsing"
/>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { computed, ref, inject } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from 'stores/auth'
import type { WebApp } from '@twa-dev/types'
const tg = inject('tg') as WebApp
const router = useRouter()
const authStore = useAuthStore()
@@ -68,16 +82,22 @@
{ 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' }
{ id: 8, name: 'account__terms_of_use', icon: 'mdi-book-open-variant-outline', description: '', iconColor: 'grey', pathName: 'terms' },
{ id: 9, name: 'account__privacy', icon: 'mdi-lock-outline', description: '', iconColor: 'grey', pathName: 'privacy' },
{ id: 10, name: 'account__stop_using', icon: 'mdi-close-box-outline', description: 'account__stop_using_description', iconColor: 'negative', pathName: 'stop_using' }
]))
const displayItems = computed(() => (
items.value.filter((item: ItemList) => !('display' in item) || item.display === true)
))
const showStopUsingDialog = ref(false)
async function goTo (path: string) {
await router.push({ name: path })
if (path !== 'stop_using') {
await router.push({ name: path })
} else {
showStopUsingDialog.value = true
}
}
async function logout () {
@@ -85,6 +105,10 @@
await router.push({ name: 'login' })
}
function onConfirmStopUsing () {
tg.close()
}
</script>
<style lang="scss">

View File

@@ -17,7 +17,7 @@
<pn-scroll-list>
<div
v-if="chat"
class="flex column items-center q-pa-md q-pb-lg"
class="flex column items-center q-pa-md"
>
<pn-auto-avatar
:img="chat.logo"
@@ -25,17 +25,23 @@
size="100px"
rounded
/>
<div
class="flex row items-start justify-center q-pt-md text-bold"
align="center"
>
{{ chat.name }}
</div>
<div
v-if="chat.description"
class="flex row items-start justify-center q-pt-md"
class="flex row items-start justify-center text-caption"
align="center"
>
{{ 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 class="text-caption text-bold q-py-none" header>
{{ $t('chat_card__members') + ' (' + chatUsers.length +')' }}
</q-item-label>
<q-item
v-for="item in chatUsers"
@@ -61,7 +67,7 @@
</q-badge>
{{item.section1}}
</q-item-label>
<q-item-label lines="1" caption v-if="item.section3">
<q-item-label lines="2" caption v-if="item.section3">
{{item.section3}}
</q-item-label>
<q-item-label caption lines="2">
@@ -113,9 +119,9 @@
watch(() => chatsStore.isInit, initChat)
function onSubmit () {
async function onSubmit () {
if (chat.value && chat.value.invite_link) tg.openTelegramLink(chat.value.invite_link)
if (chat && chat.value) chatsStore.getChatUsers(chat.value.id)
if (chat.value) await chatsStore.getChatUsers(chat.value.id)
}
const users = usersStore.getUsers
@@ -123,8 +129,8 @@
const { userSection } = useUserSection()
const chatUsers = computed(() => {
if (!chat || !chat.value) return []
const idSet = new Set(chat.value.chat_users)
if (!chat.value) return []
const idSet = new Set(chat.value.users)
const arr = users.filter(el => idSet.has(el.id))
return arr.map(el => ({
...el,

View File

@@ -5,18 +5,23 @@
title="company_edit__title_card"
btnText="company_edit__btn"
@update="updateCompany"
>
<template #myCompany v-if="company.is_own">
<div class="q-mb-md flex w100 justify-center">
<div class="flex items-center text-amber-10">
<q-icon name="star" class="q-pr-xs"/>
{{ $t('company_edit__my_company') }}
>
<template #myCompany v-if="company.is_own">
<div class="q-mb-md flex w100 justify-center">
<div class="flex items-center text-amber-10">
<q-icon name="star" class="q-pr-xs"/>
{{ $t('company_edit__my_company') }}
</div>
<div class="flex column">
<div class="text-caption" align="center" style="white-space: pre-wrap;">
{{ $t('company_edit__my_company_hint') }}
</div>
<div class="text-caption text-primary cursor-pointer" align="center" @click="goMyCompany">
{{ $t('company_edit__my_company_href') }}
</div>
</div>
</div>
<div class="text-caption" align="center" style="white-space: pre-wrap;">
{{ $t('company_edit__my_company_hint') }}
</div>
</div>
</template>
</template>
</company-block>
</template>
@@ -50,6 +55,10 @@
if (isInit) initCompany()
})
async function goMyCompany () {
await router.push({ name: 'your_company' })
}
async function updateCompany(companyData: CompanyParams) {
if (companyId.value) {
await companiesStore.update(companyId.value, companyData)

View File

@@ -1,17 +1,27 @@
<template>
<pn-page-card>
<template #title>
{{$t('company__mask')}}
<q-btn
v-if="!checkIsDirty(mask, originalMask)"
@click = "updateCompanies()"
flat round
icon="mdi-check"
/>
{{$t('mask__title')}}
</template>
<template #footer>
<q-btn
rounded color="primary"
class="w100 q-mt-md q-mb-xs fix-disabled-btn"
@click="updateCompanies()"
>
{{ $t('mask__btn_ok') }}
</q-btn>
</template>
<pn-scroll-list>
<template #card-body-header>
<div style="min-height: var(--top-raduis);"/>
<div class="q-ma-sm q-pa-sm bg-grey-11 flex no-wrap items-center" style="border-radius: var(--top-raduis);">
<q-icon name="mdi-information q-pr-xs" size="sm" color="primary"/>
<span class="text-caption">
{{ $t('mask__info_block')}}
</span>
</div>
</template>
<q-table
flat
@@ -48,7 +58,6 @@
</q-th>
</q-tr>
</template>
<template #body="props">
<q-tr :props="props">
@@ -144,7 +153,6 @@
<q-card-section class="q-pt-sm">
<p>{{ $t('mask__help_message1')}}</p>
<p>{{ $t('mask__help_message2')}}</p>
<p>{{ $t('mask__help_message3')}}</p>
</q-card-section>
</q-card>
</q-dialog>
@@ -152,9 +160,8 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ref, computed, watch } from 'vue'
import { useCompaniesStore } from 'stores/companies'
import type { CompanyMask } from 'types/Company'
import { useRouter } from 'vue-router'
const showDialogHelp = ref<boolean>(false)
@@ -164,6 +171,12 @@
const companies = computed(() => companiesStore.companies)
const companiesMask = computed(() => companiesStore.companiesMask)
const tableColumns = [
{ name: 'checkbox', field: 'checkbox', label: 'checkbox', sortable: false, style: "width: 10%" },
{ name: 'name', field: 'name', align: 'left' as const, label: 'name', sortable: false, style: "width: 45% !important; text-wrap: auto" },
{ name: 'visible', field: 'visible', align: 'center' as const, label: 'visible', sortable: false, style: "width: 45% !important" }
]
interface DisplayCompany {
id: number
name: string
@@ -174,15 +187,36 @@
hasFocus: boolean
}
//
const displayCompanies = ref<DisplayCompany[]>([])
const originalMask = ref<CompanyMask[]>([])
const tableColumns = [
{ name: 'checkbox', field: 'checkbox', label: 'checkbox', sortable: false, style: "width: 10%" },
{ name: 'name', field: 'name', align: 'left' as const, label: 'name', sortable: false, style: "width: 45% !important; text-wrap: auto" },
{ name: 'visible', field: 'visible', align: 'center' as const, label: 'visible', sortable: false, style: "width: 45% !important" }
]
watch(
() => [companies.value, companiesMask.value],
() => {
updateDisplayCompanies()
},
{ immediate: true, deep: true }
)
function updateDisplayCompanies() {
if (companies.value.length === 0 || companiesMask.value.length === 0) return
displayCompanies.value = companies.value
.filter(el => !el.is_own)
.map(el => ({
id: el.id,
name: el.name,
logo: el.logo ?? '',
masked: companiesStore.checkCompanyMasked(el.id),
company_list: getList(el.id),
isMenuOpen: false,
hasFocus: false
}))
}
function getList (companyId: number) {
const company = companiesMask.value.find(el => Number(el.company_id) === companyId)
return company?.company_list ?? []
}
const mask = computed(() => displayCompanies.value
.filter(el => el.masked)
@@ -193,8 +227,8 @@
.sort((a, b) => compareNumbers(a.company_id, b.company_id))
)
function getCompanyById(id: number) {
return companies.value.find(c => c.id === id);
function getCompanyById (id: number) {
return companies.value.find(c => c.id === id)
}
function companiesSelect (id :number) {
@@ -220,61 +254,6 @@
return a < b ? -1 : 1
}
function checkIsDirty (arr1: CompanyMask[], arr2: CompanyMask[]): boolean {
// Проверка длины массивов
if (arr1.length !== arr2.length) return false
// Функция для нормализации объекта в строковый ключ
const getKey = (obj: { company_id: number; company_list: number[] }): string => {
const sortedList = [...obj.company_list].sort(compareNumbers)
return `${obj.company_id}|${sortedList.join(',')}`
}
// Создаем Map для подсчета объектов в первом массиве
const countMap = new Map<string, number>()
for (const item of arr1) {
const key = getKey(item)
countMap.set(key, (countMap.get(key) || 0) + 1)
}
// Проверяем объекты второго массива
for (const item of arr2) {
const key = getKey(item)
const count = countMap.get(key) || 0
if (count === 0) return false
countMap.set(key, count - 1)
}
return true
}
onMounted(() => {
function getList (companyId: number) {
const company = companiesMask.value.find(el => el.company_id === companyId)
return company?.company_list ?? []
}
displayCompanies.value = companies.value
.filter(el => !el.is_own)
.map(el => ({
id: el.id,
name: el.name,
logo: el?.logo ?? '',
masked: companiesStore.checkCompanyMasked(el.id),
company_list: getList(el.id),
isMenuOpen: false,
hasFocus: false
}))
console.log(companies.value)
console.log(displayCompanies.value)
originalMask.value = [ ...(companiesMask.value.sort((a, b) => compareNumbers(a.company_id, b.company_id))) ]
})
</script>
<style scoped lang="scss">
@@ -306,4 +285,3 @@
color: 'brand';
}
</style>

View File

@@ -74,8 +74,9 @@
<q-btn
@click="sendAuth()"
color="primary"
class="w100 q-my-md"
class="w100 q-my-md fix-disabled-btn"
:disabled="!isEmailValid || !isPasswordValid"
:unelevated="!isEmailValid || !isPasswordValid"
>
{{$t('login__sign_in')}}
</q-btn>

View File

@@ -69,17 +69,29 @@
{{item.description}}
</q-item-label>
</q-item-section>
<q-item-section side class="text-caption ">
<div class="flex items-center column">
<div class="flex items-center">
<q-icon name="mdi-chat-outline"/>
<span>{{ item.chat_count }} </span>
<q-item-section side top>
<q-item-label lines="1" class="text-caption">
<div class="flex items-center">
<div class="flex items-center">
<q-icon name="mdi-chat-outline"/>
<span>{{ item.chat_count }} </span>
</div>
<div class="flex items-center q-ml-xs">
<q-icon name="mdi-account-outline"/>
<span>{{ item.user_count }}</span>
</div>
</div>
<div class="flex items-center">
<q-icon name="mdi-account-outline"/>
<span>{{ item.user_count }}</span>
</div>
</div>
</q-item-label>
<q-item-label>
<q-btn
color="grey"
size="sm"
dense flat round
icon="mdi-pencil"
class="q-ml-sm"
@click.stop="editProject(item.id)"
/>
</q-item-label>
</q-item-section>
</q-item>
</q-slide-item>
@@ -225,6 +237,10 @@
await router.push({ name: 'chats', params: { id }})
}
async function editProject (id: number) {
await router.push({ name: 'project_info', params: { id }})
}
async function goAccount () {
await router.push({ name: 'account' })
}

View File

@@ -0,0 +1,104 @@
<template>
<pn-page-card>
<template #title>
{{$t('agreements__title')}}
</template>
<pn-scroll-list>
<div class="flex column w100 q-pa-md q-gutter-y-md">
<div>
{{ $t('agreements__description') }}
</div>
<div class="flex items-center no-wrap">
<q-checkbox v-model="agreement" val="1" class="q-pr-sm"/>
<span>
{{ $t('agreements__checkbox_agreement_terms') + ' ' }}
<span
@click="router.push({ name: 'terms' })"
class="cursor-pointer"
style="text-decoration: underline;"
>
{{ $t('agreements__checkbox_agreement_terms_doc') }}
</span>
</span>
</div>
<div class="flex items-center no-wrap">
<q-checkbox v-model="agreement" val="2" class="q-pr-sm"/>
<span>
{{ $t('agreements__checkbox_agreement_consent') + ' ' }}
<span
@click="router.push({ name: 'consent' })"
class="cursor-pointer"
style="text-decoration: underline;"
>
{{ $t('agreements__checkbox_agreement_consent_doc') }}
</span>
{{ ' ' + $t('agreements__checkbox_agreement_privacy') + ' ' }}
<span
@click="router.push({ name: 'consent' })"
class="cursor-pointer"
style="text-decoration: underline;"
>
{{ $t('agreements__checkbox_agreement_privacy_doc') }}
</span>
</span>
</div>
</div>
</pn-scroll-list>
<template #footer>
<div class="flex no-wrap justify-center w100 q-gutter-x-md q-pb-md">
<q-btn
flat
color="negative"
no-caps
style="opacity: 0.6"
@click="onDecline"
class="col"
>
<div class="flex column items-center">
{{ $t('agreements__btn_decline') }}
<span class="text-caption">
{{ $t('agreements__btn_decline_description') }}
</span>
</div>
</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>
</template>
</pn-page-card>
</template>
<script setup lang="ts">
import { ref, inject } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from 'stores/auth'
import type { WebApp } from '@twa-dev/types'
const tg = inject('tg') as WebApp
function onDecline () {
tg.close()
}
const authStore = useAuthStore()
const router = useRouter()
async function onSubmit () {
await authStore.termsAccepted()
await router.push({ name: 'projects' })
}
const agreement = ref([])
</script>
<style>
</style>

View File

@@ -0,0 +1,10 @@
<template>
<doc-block type="consent"/>
</template>
<script setup lang="ts">
import docBlock from 'components/docBlock.vue'
</script>
<style>
</style>

View File

@@ -52,12 +52,17 @@
</div>
<q-btn
@click="editProject"
flat round
color="white"
icon="edit"
size="md"
/>
@click="toProjects"
flat
no-caps
dense
class="q-ml-xl"
>
<span class="flex items-center no-wrap text-caption">
{{ $t('header__to_projects') }}
<q-icon name="mdi-chevron-right"/>
</span>
</q-btn>
</div>
</template>
@@ -93,9 +98,8 @@
expandProjectInfo.value = !expandProjectInfo.value
}
async function editProject () {
if (currentProjectId.value)
await router.push({ name: 'project_info', params: { id: currentProjectId.value } })
async function toProjects () {
await router.push({ name: 'projects' })
}
interface sizeParams {

View File

@@ -45,7 +45,7 @@
<q-item-label lines="1" class="text-bold" v-if="item.section1">
{{item.section1}}
</q-item-label>
<q-item-label lines="1" caption v-if="item.section3">
<q-item-label lines="2" caption v-if="item.section3">
{{item.section3}}
</q-item-label>
<q-item-label caption lines="2">
@@ -172,7 +172,7 @@
<q-item-label lines="1" class="text-bold" v-if="item.section1">
{{item.section1}}
</q-item-label>
<q-item-label lines="1" caption v-if="item.section3">
<q-item-label lines="2" caption v-if="item.section3">
{{item.section3}}
</q-item-label>
<q-item-label caption lines="2">