update
This commit is contained in:
BIN
i18n-2.xlsm
BIN
i18n-2.xlsm
Binary file not shown.
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "projectsnode",
|
"name": "tgcrew",
|
||||||
"version": "0.0.1",
|
"version": "0.1.01",
|
||||||
"description": "telegram miniapp",
|
"description": "telegram admin miniapp",
|
||||||
"productName": "projectsNode",
|
"productName": "tgCrew (admin)",
|
||||||
"author": "Alex Mart",
|
"author": "Alex Mart",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
1
public/resources
Symbolic link
1
public/resources
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
F:/project/tgCrew/backend/public/resources
|
||||||
@@ -117,6 +117,16 @@ export default defineConfig((ctx) => {
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ws: true
|
ws: true
|
||||||
},
|
},
|
||||||
|
// необходимо создать simlink
|
||||||
|
// mklink /D "F:\project\tgCrew\admin\public\resources" "F:\project\tgCrew\backend\public\resources"
|
||||||
|
'/resources': {
|
||||||
|
target: 'http://localhost:3000',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
pathRewrite: {
|
||||||
|
'^/resources': '/resources'
|
||||||
|
}
|
||||||
|
}
|
||||||
/* '/ws': {
|
/* '/ws': {
|
||||||
target: 'ws://localhost:3000', // или wss:// для HTTPS
|
target: 'ws://localhost:3000', // или wss:// для HTTPS
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
@@ -126,7 +136,7 @@ export default defineConfig((ctx) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// https: true,
|
// https: true,
|
||||||
// open: true // opens browser window automatically
|
open: false // opens browser window automatically
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#framework
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#framework
|
||||||
@@ -244,7 +254,7 @@ export default defineConfig((ctx) => {
|
|||||||
builder: {
|
builder: {
|
||||||
// https://www.electron.build/configuration/configuration
|
// https://www.electron.build/configuration/configuration
|
||||||
|
|
||||||
appId: 'projectsnode'
|
appId: 'tgcrew'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,23 @@
|
|||||||
{{ $t(title) }}
|
{{ $t(title) }}
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<q-btn
|
<div class="w100 q-pa-md">
|
||||||
rounded color="primary"
|
<q-btn
|
||||||
class="w100 q-mt-md q-mb-xs fix-disabled-btn"
|
rounded color="primary"
|
||||||
:disable="!isFormValid"
|
class="w100 fix-disabled-btn"
|
||||||
@click = "onSubmit"
|
:disable="!isFormValid"
|
||||||
>
|
@click = "onSubmit"
|
||||||
{{ $t(btnText) }}
|
>
|
||||||
</q-btn>
|
{{ $t(btnText) }}
|
||||||
|
</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="flex column items-center q-pa-md q-pb-sm">
|
||||||
<slot name="myCompany"/>
|
<slot name="myCompany"/>
|
||||||
<pn-image-selector
|
<pn-image-selector
|
||||||
v-model="modelValue.logo"
|
v-model="companyMod.logo"
|
||||||
:size="100"
|
:size="100"
|
||||||
:iconsize="80"
|
:iconsize="80"
|
||||||
class="q-pb-lg"
|
class="q-pb-lg"
|
||||||
@@ -27,7 +29,7 @@
|
|||||||
<q-input
|
<q-input
|
||||||
v-for="input in textInputs"
|
v-for="input in textInputs"
|
||||||
:key="input.id"
|
:key="input.id"
|
||||||
v-model.trim="modelValue[input.val]"
|
v-model.trim="companyMod[input.val]"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
class="w100"
|
class="w100"
|
||||||
@@ -38,7 +40,7 @@
|
|||||||
? 'input-fix q-pt-sm'
|
? 'input-fix q-pt-sm'
|
||||||
: 'q-pt-sm'"
|
: 'q-pt-sm'"
|
||||||
:label="input.label ? $t(input.label) : void 0"
|
:label="input.label ? $t(input.label) : void 0"
|
||||||
:rules="input.val === 'name' ? [rules[input.val]] : []"
|
:rules="input.rules"
|
||||||
no-error-icon
|
no-error-icon
|
||||||
:label-slot="Boolean(input.label)"
|
:label-slot="Boolean(input.label)"
|
||||||
>
|
>
|
||||||
@@ -57,24 +59,35 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } 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 { convertEmptyStringsToNull } from 'src/utils/helpers'
|
import { removeNullKeys } from 'utils/helpers'
|
||||||
|
|
||||||
const { t }= useI18n()
|
const { t }= useI18n()
|
||||||
|
|
||||||
const modelValue = defineModel<CompanyParams>({
|
const props = defineProps<{
|
||||||
required: true
|
|
||||||
})
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
title: string,
|
title: string,
|
||||||
btnText: string
|
btnText: string,
|
||||||
|
company?: CompanyParams
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['update'])
|
const emit = defineEmits(['update'])
|
||||||
|
|
||||||
|
type CompType = {
|
||||||
|
[K in keyof CompanyParams]: CompanyParams[K] extends number ? never : CompanyParams[K]
|
||||||
|
}
|
||||||
|
|
||||||
|
const companyMod = reactive<CompType>({
|
||||||
|
name: props.company?.name ?? '',
|
||||||
|
description: props.company?.description ?? '',
|
||||||
|
site: props.company?.site ?? '',
|
||||||
|
address: props.company?.address ?? '',
|
||||||
|
phone: props.company?.phone ?? '',
|
||||||
|
email: props.company?.email ?? '',
|
||||||
|
logo: props.company?.logo ?? null
|
||||||
|
})
|
||||||
|
|
||||||
interface TextInput {
|
interface TextInput {
|
||||||
id: number
|
id: number
|
||||||
label?: string
|
label?: string
|
||||||
@@ -83,34 +96,42 @@
|
|||||||
rules: ((value: string) => boolean | string)[]
|
rules: ((value: string) => boolean | string)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
name: (val: string) => !!val?.trim() || rulesErrorMessage['name']
|
||||||
|
}
|
||||||
|
|
||||||
const textInputs: TextInput[] = [
|
const textInputs: TextInput[] = [
|
||||||
{ id: 1, val: 'name', label: 'company_block__name', rules: [] },
|
{ id: 1, val: 'name', label: 'company_block__name', rules: [rules.name] },
|
||||||
{ id: 2, val: 'description', label: 'company_block__description', rules: [] },
|
{ id: 2, val: 'description', label: 'company_block__description', rules: [] },
|
||||||
{ id: 3, val: 'site', icon: 'mdi-web', rules: [] },
|
{ id: 3, val: 'site', icon: 'mdi-web', rules: [] },
|
||||||
{ id: 4, val: 'address', icon: 'mdi-map-marker-outline', rules: [] },
|
{ id: 4, val: 'address', icon: 'mdi-map-marker-outline', rules: [] },
|
||||||
{ id: 5, val: 'phone', icon: 'mdi-phone-outline', rules: [] },
|
{ id: 5, val: 'phone', icon: 'mdi-phone-outline', rules: [] },
|
||||||
{ id: 6, val: 'email', icon: 'mdi-email-outline', rules: [] }
|
{ id: 6, val: 'email', icon: 'mdi-email-outline', rules: [] }
|
||||||
]
|
] as const
|
||||||
|
|
||||||
const rulesErrorMessage = {
|
const rulesErrorMessage = {
|
||||||
name: t('company_block__error_name')
|
name: t('company_block__error_name')
|
||||||
}
|
}
|
||||||
|
|
||||||
const rules = {
|
|
||||||
name: (val: CompanyParams['name']) => !!val?.trim() || rulesErrorMessage['name']
|
|
||||||
}
|
|
||||||
|
|
||||||
const isFormValid = computed(() => {
|
const isFormValid = computed(() => {
|
||||||
const validations = {
|
const validations = {
|
||||||
name: rules.name(modelValue.value.name) === true
|
name: companyMod.name && rules.name(companyMod.name) === true
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(validations).every(Boolean)
|
return Object.values(validations).every(Boolean)
|
||||||
})
|
})
|
||||||
|
|
||||||
function onSubmit() {
|
function onSubmit() {
|
||||||
const cleanedData = convertEmptyStringsToNull(modelValue.value)
|
const outData = {
|
||||||
emit('update', cleanedData)
|
name: companyMod.name,
|
||||||
|
description: companyMod.description ?? (typeof props.company?.description === 'string' ? '' : null),
|
||||||
|
site: companyMod.site ?? (typeof props.company?.site === 'string' ? '' : null),
|
||||||
|
address: companyMod.address ?? (typeof props.company?.address === 'string' ? '' : null),
|
||||||
|
phone: companyMod.phone ?? (typeof props.company?.phone === 'string' ? '' : null),
|
||||||
|
email: companyMod.email ?? (typeof props.company?.email === 'string' ? '' : null),
|
||||||
|
logo: companyMod.logo
|
||||||
|
}
|
||||||
|
emit('update', removeNullKeys(outData))
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
:style="{
|
:style="{
|
||||||
height: sizePx,
|
height: sizePx,
|
||||||
maxWidth: sizePx,
|
maxWidth: sizePx,
|
||||||
borderRadius: avatar ? String(size/2) + 'px' : 'var(--top-raduis)',
|
borderRadius: avatar ? String(size/2) + 'px' : 'var(--top-radius)',
|
||||||
}"
|
}"
|
||||||
@click="showDialog = true"
|
@click="showDialog = true"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -36,8 +36,8 @@
|
|||||||
<q-card
|
<q-card
|
||||||
class="fix-card-width flex column no-scroll no-wrap q-px-none"
|
class="fix-card-width flex column no-scroll no-wrap q-px-none"
|
||||||
style="
|
style="
|
||||||
border-top-left-radius: var(--top-raduis) !important;
|
border-top-left-radius: var(--top-radius) !important;
|
||||||
border-top-right-radius: var(--top-raduis) !important;
|
border-top-right-radius: var(--top-radius) !important;
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -46,7 +46,12 @@
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex column q-mx-xs">
|
<div class="flex column q-mx-xs">
|
||||||
<span class="text-h6 ellipsis">{{ $t(title) }}</span>
|
<span
|
||||||
|
class="text-h6"
|
||||||
|
style="line-height: 1.5rem;"
|
||||||
|
>
|
||||||
|
{{ $t(title) }}
|
||||||
|
</span>
|
||||||
<span v-if="caption" class="text-grey text-caption">{{ $t(caption) }}</span>
|
<span v-if="caption" class="text-grey text-caption">{{ $t(caption) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,20 +18,20 @@
|
|||||||
<div class="flex column justify-center col-grow items-center">
|
<div class="flex column justify-center col-grow items-center">
|
||||||
<q-icon :name="icon" size="160px" class="q-pb-md"/>
|
<q-icon :name="icon" size="160px" class="q-pb-md"/>
|
||||||
<div class="text-h6 text-brand">
|
<div class="text-h6 text-brand">
|
||||||
{{message1}}
|
{{ message1 }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="message2" class="text-caption" align="center">
|
<div v-if="message2" class="text-caption text-center">
|
||||||
{{message2}}
|
{{ message2 }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
icon: string
|
icon: string
|
||||||
message1: string
|
message1: string
|
||||||
message2?: string
|
message2?: string
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<slot name="title"/>
|
<slot name="title"/>
|
||||||
</div>
|
</div>
|
||||||
<slot/>
|
<slot/>
|
||||||
<div class="bg-white w100 q-ma-none q-px-md flex items-center">
|
<div class="bg-white w100">
|
||||||
<slot name="footer"/>
|
<slot name="footer"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,12 +5,16 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="card-body-header"
|
id="card-body-header"
|
||||||
style="flex-shrink: 0; min-height: var(--top-raduis);"
|
v-if="slots['card-body-header']"
|
||||||
|
style="flex-shrink: 0; min-height: var(--top-radius);"
|
||||||
>
|
>
|
||||||
<q-resize-observer @resize="onHeaderResize"/>
|
<q-resize-observer @resize="onHeaderResize"/>
|
||||||
<slot name="card-body-header"/>
|
<slot name="card-body-header"/>
|
||||||
</div>
|
</div>
|
||||||
<div id="card-body" >
|
<div id="card-body"
|
||||||
|
class="top-rounded-card bg-white"
|
||||||
|
style="padding-top: var(--top-radius);"
|
||||||
|
>
|
||||||
<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/>
|
||||||
@@ -21,6 +25,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, nextTick } from 'vue'
|
import { ref, watch, nextTick } from 'vue'
|
||||||
|
import { useSlots } from 'vue'
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
const heightCard = ref(100)
|
const heightCard = ref(100)
|
||||||
const scrollAreaHeight = ref(100)
|
const scrollAreaHeight = ref(100)
|
||||||
@@ -37,7 +44,7 @@ async function onHeaderResize(size: sizeParams) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onBodyResize(size: sizeParams) {
|
async function onBodyResize(size: sizeParams) {
|
||||||
heightCard.value = size.height
|
heightCard.value = size.height - parseInt(String(getComputedStyle(document.documentElement).getPropertyValue('--top-radius'))) // subtract padding var(top-radius)
|
||||||
await updateScrollAreaHeight()
|
await updateScrollAreaHeight()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,11 +77,6 @@ watch(heightCard, () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.glass-card {
|
|
||||||
opacity: 1 !important;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#page-card {
|
#page-card {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|||||||
@@ -4,27 +4,30 @@
|
|||||||
{{ $t(title) }}
|
{{ $t(title) }}
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<q-btn
|
<div class="w100 q-pa-md">
|
||||||
rounded color="primary"
|
<q-btn
|
||||||
class="w100 q-mt-md q-mb-xs fix-disabled-btn"
|
rounded color="primary"
|
||||||
:disable="!isFormValid"
|
class="w100 fix-disabled-btn"
|
||||||
@click = "onSubmit"
|
:disable="!isFormValid"
|
||||||
>
|
@click = "onSubmit"
|
||||||
{{ $t(btnText) }}
|
>
|
||||||
</q-btn>
|
{{ $t(btnText) }}
|
||||||
|
</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="flex column items-center q-pa-md q-pb-sm">
|
||||||
<pn-image-selector
|
<pn-image-selector
|
||||||
v-model="modelValue.logo"
|
v-model="projectMod.logo"
|
||||||
:size="100"
|
:size="100"
|
||||||
:iconsize="80"
|
:iconsize="80"
|
||||||
|
type="rounded"
|
||||||
class="q-pb-lg"
|
class="q-pb-lg"
|
||||||
/>
|
/>
|
||||||
<div class="q-gutter-y-lg w100">
|
<div class="q-gutter-y-lg w100">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="modelValue.name"
|
v-model="projectMod.name"
|
||||||
no-error-icon
|
no-error-icon
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
@@ -38,7 +41,7 @@
|
|||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model="modelValue.description"
|
v-model="projectMod.description"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
autogrow
|
autogrow
|
||||||
@@ -53,44 +56,52 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } 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 { convertEmptyStringsToNull } from 'src/utils/helpers'
|
import { removeNullKeys } from 'utils/helpers'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const modelValue = defineModel<ProjectParams>({
|
const props = defineProps<{
|
||||||
required: true
|
|
||||||
})
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
title: string,
|
title: string,
|
||||||
btnText: string
|
btnText: string,
|
||||||
|
project?: ProjectParams
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['update'])
|
const emit = defineEmits(['update'])
|
||||||
|
|
||||||
|
const projectMod = reactive({
|
||||||
|
name: props.project?.name ?? '',
|
||||||
|
description: props.project?.description ?? '',
|
||||||
|
logo: props.project?.logo ?? null
|
||||||
|
})
|
||||||
|
|
||||||
const rulesErrorMessage = {
|
const rulesErrorMessage = {
|
||||||
name: t('project_block__error_name')
|
name: t('project_block__error_name')
|
||||||
}
|
}
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
name: (val: ProjectParams['name']) => !!val?.trim() || rulesErrorMessage['name']
|
name: (val: string) => !!val?.trim() || rulesErrorMessage['name']
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFormValid = computed(() => {
|
const isFormValid = computed(() => {
|
||||||
const validations = {
|
const validations = {
|
||||||
name: rules.name(modelValue.value.name) === true
|
name: projectMod.name && rules.name(projectMod.name) === true
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(validations).every(Boolean)
|
return Object.values(validations).every(Boolean)
|
||||||
})
|
})
|
||||||
|
|
||||||
function onSubmit() {
|
function onSubmit() {
|
||||||
const cleanedData = convertEmptyStringsToNull(modelValue.value)
|
const outData = {
|
||||||
emit('update', cleanedData)
|
name: projectMod.name,
|
||||||
|
description: projectMod.description ?? (typeof props.project?.description === 'string' ? '' : null),
|
||||||
|
logo: projectMod.logo
|
||||||
|
}
|
||||||
|
emit('update', removeNullKeys(outData))
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -4,43 +4,45 @@
|
|||||||
{{ $t(title) }}
|
{{ $t(title) }}
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<q-btn
|
<div class="w100 q-pa-md">
|
||||||
rounded color="primary"
|
<q-btn
|
||||||
class="w100 q-mt-md q-mb-xs fix-disabled-btn"
|
rounded color="primary"
|
||||||
@click = "onSubmit"
|
class="w100 fix-disabled-btn"
|
||||||
>
|
@click = "onSubmit"
|
||||||
{{ $t(btnText) }}
|
>
|
||||||
</q-btn>
|
{{ $t(btnText) }}
|
||||||
|
</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="flex column items-center q-pa-md q-pb-sm">
|
||||||
<div class="relative-position">
|
<div class="relative-position">
|
||||||
<pn-auto-avatar
|
<pn-auto-avatar
|
||||||
:img="modelValue.photo"
|
:img="userMod.photo"
|
||||||
:name="tname"
|
:name="tname"
|
||||||
size="100px"
|
size="100px"
|
||||||
class="q-pb-lg"
|
class="q-pb-lg"
|
||||||
:style="!userStatus ? {} : { filter: 'grayscale(100%)'}"
|
:style="!userStatus ? {} : { filter: 'grayscale(100%)'}"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="userStatus"
|
v-if="userStatus"
|
||||||
class="absolute-center text-h4 text-bold q-pa-sm text-center"
|
class="absolute-center text-h4 text-bold q-pa-sm text-center"
|
||||||
:class ="'status-' + userStatus.status"
|
:class ="'status-' + userStatus.status"
|
||||||
>
|
>
|
||||||
{{ $t(userStatus.text) }}
|
{{ $t(userStatus.text) }}
|
||||||
</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">
|
||||||
<div class="flex column justify-center">
|
<div class="flex column justify-center">
|
||||||
<div class="text-bold q-pr-xs text-center" align="center">{{ tname }}</div>
|
<div class="text-bold q-pr-xs text-center" align="center">{{ tname }}</div>
|
||||||
<div caption class="text-blue text-caption" align="center" v-if="modelValue.username">{{ modelValue.username }}</div>
|
<div caption class="text-blue text-caption" align="center" v-if="user?.username">{{ user.username }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="q-gutter-y-lg w100">
|
<div class="q-gutter-y-lg w100">
|
||||||
<q-input
|
<q-input
|
||||||
v-model.trim="modelValue.fullname"
|
v-model.trim="userMod.fullname"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
class = "w100"
|
class = "w100"
|
||||||
@@ -48,7 +50,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<q-select
|
<q-select
|
||||||
v-model="modelValue.company_id"
|
v-model="userMod.company_id"
|
||||||
:options="displayCompanies"
|
:options="displayCompanies"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
@@ -84,7 +86,7 @@
|
|||||||
</q-select>
|
</q-select>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model.trim="modelValue.department"
|
v-model.trim="userMod.department"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
class = "w100 q-pt-sm"
|
class = "w100 q-pt-sm"
|
||||||
@@ -92,7 +94,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model.trim="modelValue.role"
|
v-model.trim="userMod.role"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
class = "w100 q-pt-sm"
|
class = "w100 q-pt-sm"
|
||||||
@@ -100,7 +102,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model.trim="modelValue.phone"
|
v-model.trim="userMod.phone"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
class = "w100 q-pt-sm"
|
class = "w100 q-pt-sm"
|
||||||
@@ -111,7 +113,7 @@
|
|||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model.trim="modelValue.email"
|
v-model.trim="userMod.email"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
class = "w100 q-pt-sm"
|
class = "w100 q-pt-sm"
|
||||||
@@ -129,30 +131,31 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } 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 { convertEmptyStringsToNull } from 'src/utils/helpers'
|
import { removeNullKeys } from 'utils/helpers'
|
||||||
import type { User } from 'types/User'
|
import type { User } from 'types/User'
|
||||||
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const modelValue = defineModel<User>({
|
const props = defineProps<{
|
||||||
required: true
|
|
||||||
})
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
title: string,
|
title: string,
|
||||||
btnText: string
|
btnText: string,
|
||||||
|
user?: User
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['update'])
|
const emit = defineEmits(['update'])
|
||||||
|
|
||||||
function onSubmit() {
|
const userMod = reactive({
|
||||||
const cleanedData = convertEmptyStringsToNull(modelValue.value)
|
fullname: props.user?.fullname ?? '',
|
||||||
emit('update', cleanedData)
|
company_id: props.user?.company_id ?? null,
|
||||||
}
|
department: props.user?.department ?? '',
|
||||||
|
role: props.user?.role ?? '',
|
||||||
|
phone: props.user?.phone ?? '',
|
||||||
|
email: props.user?.email ?? '',
|
||||||
|
photo: props.user?.photo ?? null
|
||||||
|
})
|
||||||
|
|
||||||
const companiesStore = useCompaniesStore()
|
const companiesStore = useCompaniesStore()
|
||||||
const companies = computed(() => companiesStore.companies)
|
const companies = computed(() => companiesStore.companies)
|
||||||
@@ -171,18 +174,31 @@
|
|||||||
])
|
])
|
||||||
|
|
||||||
const tname = computed(() =>
|
const tname = computed(() =>
|
||||||
[modelValue.value?.firstname, modelValue.value?.lastname]
|
[props.user?.firstname, props.user?.lastname]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ')
|
.join(' ')
|
||||||
)
|
)
|
||||||
|
|
||||||
const userStatus = computed(() => {
|
const userStatus = computed(() => {
|
||||||
if (modelValue.value.is_blocked) return { status: 'blocked', text: 'user_block__user_blocked' }
|
if (props.user?.is_blocked) return { status: 'blocked', text: 'user_block__user_blocked' }
|
||||||
if (modelValue.value.is_leave) return { status: 'leave', text: 'user_block__user_leave' }
|
if (props.user?.is_leave) return { status: 'leave', text: 'user_block__user_leave' }
|
||||||
if (!modelValue.value.is_terms_accepted) return { status: 'pending', text: 'user_block__user_pending' }
|
if (!props.user?.is_terms_accepted) return { status: 'pending', text: 'user_block__user_pending' }
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function onSubmit() {
|
||||||
|
const outData = {
|
||||||
|
fullname: userMod.fullname ?? (typeof props.user?.fullname === 'string' ? '' : null),
|
||||||
|
company_id: userMod.company_id,
|
||||||
|
department: userMod.department ?? (typeof props.user?.department === 'string' ? '' : null),
|
||||||
|
role: userMod.role ?? (typeof props.user?.role === 'string' ? '' : null),
|
||||||
|
phone: userMod.phone ?? (typeof props.user?.phone === 'string' ? '' : null),
|
||||||
|
email: userMod.email ?? (typeof props.user?.email === 'string' ? '' : null),
|
||||||
|
photo: userMod.photo
|
||||||
|
}
|
||||||
|
emit('update', removeNullKeys(outData))
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ body, html, #q-app {
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--body-width: 600px;
|
--body-width: 600px;
|
||||||
--top-raduis: 12px;
|
--top-radius: 12px;
|
||||||
--logo-color-bg-white: grey;
|
--logo-color-bg-white: grey;
|
||||||
--dynamic-font-size: 16px;
|
--dynamic-font-size: 16px;
|
||||||
}
|
}
|
||||||
@@ -61,8 +61,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.top-rounded-card {
|
.top-rounded-card {
|
||||||
border-top-left-radius: var(--top-raduis);
|
border-top-left-radius: var(--top-radius) !important;
|
||||||
border-top-right-radius: var(--top-raduis);
|
border-top-right-radius: var(--top-radius) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-card {
|
||||||
|
border-radius: var(--top-radius) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-card {
|
||||||
|
background-color: rgb(255, 255, 255, 0.91) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.orline {
|
.orline {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -92,6 +92,7 @@
|
|||||||
))
|
))
|
||||||
|
|
||||||
const showStopUsingDialog = ref(false)
|
const showStopUsingDialog = ref(false)
|
||||||
|
|
||||||
async function goTo (path: string) {
|
async function goTo (path: string) {
|
||||||
if (path !== 'stop_using') {
|
if (path !== 'stop_using') {
|
||||||
await router.push({ name: path })
|
await router.push({ name: path })
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
{{$t('agreements__title')}}
|
{{$t('agreements__title')}}
|
||||||
</template>
|
</template>
|
||||||
<pn-scroll-list>
|
<pn-scroll-list>
|
||||||
<div class="flex column w100 q-pa-md q-gutter-y-md">
|
<div class="flex column w100 q-py-md q-px-lg q-gutter-y-sm text-caption">
|
||||||
<div>
|
<div class="text-bold">
|
||||||
<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>
|
||||||
<div class="flex items-center no-wrap text-caption">
|
<div class="flex items-center no-wrap">
|
||||||
<q-checkbox v-model="agreement" val="1" class="q-pr-sm"/>
|
<q-checkbox v-model="agreement" val="1" class="q-pr-sm"/>
|
||||||
<span>
|
<span>
|
||||||
{{ $t('agreements__checkbox_agreement_terms') + ' ' }}
|
{{ $t('agreements__checkbox_agreement_terms') + ' ' }}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center no-wrap text-caption">
|
<div class="flex items-center no-wrap">
|
||||||
<q-checkbox v-model="agreement" val="2" class="q-pr-sm"/>
|
<q-checkbox v-model="agreement" val="2" class="q-pr-sm"/>
|
||||||
<span>
|
<span>
|
||||||
{{ $t('agreements__checkbox_agreement_consent') + ' ' }}
|
{{ $t('agreements__checkbox_agreement_consent') + ' ' }}
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</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-pb-md">
|
<div class="flex no-wrap justify-center w100 q-gutter-x-md q-py-md">
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
color="negative"
|
color="negative"
|
||||||
|
|||||||
@@ -4,14 +4,16 @@
|
|||||||
{{ $t('chat_card__title') }}
|
{{ $t('chat_card__title') }}
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<q-btn
|
<div class="w100 q-pa-md">
|
||||||
rounded color="primary"
|
<q-btn
|
||||||
class="w100 q-mt-md q-mb-xs"
|
rounded color="primary"
|
||||||
@click = "onSubmit"
|
class="w100"
|
||||||
v-if="chat && chat.invite_link"
|
@click="onSubmit"
|
||||||
>
|
v-if="chat"
|
||||||
{{ $t("chat_card__go_chat") }}
|
>
|
||||||
</q-btn>
|
{{ $t("chat_card__go_chat") }}
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<pn-scroll-list>
|
<pn-scroll-list>
|
||||||
@@ -118,18 +120,18 @@
|
|||||||
watch(() => chatsStore.isInit, initChat)
|
watch(() => chatsStore.isInit, initChat)
|
||||||
|
|
||||||
async function onSubmit () {
|
async function onSubmit () {
|
||||||
if (chat.value && chat.value.invite_link) tg.openTelegramLink(chat.value.invite_link)
|
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)
|
if (chat.value) await chatsStore.getChatUsers(chat.value.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = usersStore.getUsers
|
const users = computed(() => usersStore.users)
|
||||||
const companiesStore = useCompaniesStore()
|
const companiesStore = useCompaniesStore()
|
||||||
const { userSection } = useUserSection()
|
const { userSection } = useUserSection()
|
||||||
|
|
||||||
const chatUsers = computed(() => {
|
const chatUsers = computed(() => {
|
||||||
if (!chat.value) return []
|
if (!chat.value) return []
|
||||||
const idSet = new Set(chat.value.users)
|
const idSet = new Set(chat.value.users)
|
||||||
const arr = users.filter(el => idSet.has(el.id))
|
const arr = users.value.filter(el => idSet.has(el.id))
|
||||||
return arr.map(el => ({
|
return arr.map(el => ({
|
||||||
...el,
|
...el,
|
||||||
...userSection(el),
|
...userSection(el),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<company-block
|
<company-block
|
||||||
v-model="newCompany"
|
|
||||||
title="company_add__title_card"
|
title="company_add__title_card"
|
||||||
btnText="company_add__btn"
|
btnText="company_add__btn"
|
||||||
@update="addCompany"
|
@update="addCompany"
|
||||||
@@ -8,7 +7,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import companyBlock from 'components/companyBlock.vue'
|
import companyBlock from 'components/companyBlock.vue'
|
||||||
import { useCompaniesStore } from 'stores/companies'
|
import { useCompaniesStore } from 'stores/companies'
|
||||||
@@ -17,8 +15,6 @@
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const companiesStore = useCompaniesStore()
|
const companiesStore = useCompaniesStore()
|
||||||
|
|
||||||
const newCompany = ref<CompanyParams>({} as CompanyParams)
|
|
||||||
|
|
||||||
async function addCompany(companyData: CompanyParams) {
|
async function addCompany(companyData: CompanyParams) {
|
||||||
await companiesStore.add(companyData)
|
await companiesStore.add(companyData)
|
||||||
router.go(-1)
|
router.go(-1)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<company-block
|
<company-block
|
||||||
v-if="company"
|
:company
|
||||||
v-model="company"
|
|
||||||
title="company_edit__title_card"
|
title="company_edit__title_card"
|
||||||
btnText="company_edit__btn"
|
btnText="company_edit__btn"
|
||||||
@update="updateCompany"
|
@update="updateCompany"
|
||||||
@@ -12,6 +11,7 @@
|
|||||||
<q-icon name="star" class="q-pr-xs"/>
|
<q-icon name="star" class="q-pr-xs"/>
|
||||||
{{ $t('company_edit__my_company') }}
|
{{ $t('company_edit__my_company') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex column">
|
<div class="flex column">
|
||||||
<div class="text-caption" align="center" style="white-space: pre-wrap;">
|
<div class="text-caption" align="center" style="white-space: pre-wrap;">
|
||||||
{{ $t('company_edit__my_company_hint') }}
|
{{ $t('company_edit__my_company_hint') }}
|
||||||
@@ -26,45 +26,29 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import companyBlock from 'components/companyBlock.vue'
|
import companyBlock from 'components/companyBlock.vue'
|
||||||
import { useCompaniesStore } from 'stores/companies'
|
import { useCompaniesStore } from 'stores/companies'
|
||||||
import { parseIntString } from 'src/utils/helpers'
|
import { parseIntString } from 'utils/helpers'
|
||||||
import type { CompanyParams } from 'types/Company'
|
import type { CompanyParams } from 'types/Company'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const companiesStore = useCompaniesStore()
|
const companiesStore = useCompaniesStore()
|
||||||
|
|
||||||
const company = ref<CompanyParams | null>(null)
|
|
||||||
const companyId = computed(() => parseIntString(route.params.companyId))
|
const companyId = computed(() => parseIntString(route.params.companyId))
|
||||||
|
|
||||||
function initCompany() {
|
const company = companyId.value && companiesStore.companyById(companyId.value) || {} as CompanyParams
|
||||||
if (companiesStore.isInit && companyId.value) {
|
|
||||||
const foundCompany = companiesStore.companyById(companyId.value)
|
|
||||||
if (foundCompany) {
|
|
||||||
company.value = foundCompany as CompanyParams
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (companiesStore.isInit) initCompany()
|
|
||||||
|
|
||||||
watch(() => companiesStore.isInit, (isInit) => {
|
|
||||||
if (isInit) initCompany()
|
|
||||||
})
|
|
||||||
|
|
||||||
async function goMyCompany () {
|
async function goMyCompany () {
|
||||||
await router.push({ name: 'your_company' })
|
await router.push({ name: 'your_company' })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateCompany (companyData: CompanyParams) {
|
async function updateCompany (company: CompanyParams) {
|
||||||
if (companyId.value) {
|
if (!companyId .value || !company) return
|
||||||
await companiesStore.update(companyId.value, companyData)
|
await companiesStore.update(companyId.value, company)
|
||||||
router.go(-1)
|
router.go(-1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<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-raduis);">
|
<div class="q-ma-sm q-pa-sm bg-grey-11 flex no-wrap items-center" style="border-radius: var(--top-radius);">
|
||||||
<q-icon name="mdi-information q-pr-xs" size="sm" color="primary"/>
|
<q-icon name="mdi-information q-pr-xs" size="sm" color="primary"/>
|
||||||
<span class="text-caption">
|
<span class="text-caption">
|
||||||
{{ $t('mask__info_block')}}
|
{{ $t('mask__info_block')}}
|
||||||
|
|||||||
@@ -238,6 +238,6 @@
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.login-card {
|
.login-card {
|
||||||
opacity: 0.9 !important;
|
opacity: 0.9 !important;
|
||||||
border-radius: var(--top-raduis);
|
border-radius: var(--top-radius);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<project-block
|
<project-block
|
||||||
v-model="newProject"
|
|
||||||
title="project_create__title_card"
|
title="project_create__title_card"
|
||||||
btnText="project_create__btn"
|
btnText="project_create__btn"
|
||||||
@update="addProject"
|
@update="addProject"
|
||||||
@@ -8,7 +7,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import projectBlock from 'components/projectBlock.vue'
|
import projectBlock from 'components/projectBlock.vue'
|
||||||
import { useProjectsStore } from 'stores/projects'
|
import { useProjectsStore } from 'stores/projects'
|
||||||
@@ -17,8 +15,6 @@
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const projectsStore = useProjectsStore()
|
const projectsStore = useProjectsStore()
|
||||||
|
|
||||||
const newProject = ref<ProjectParams>({} as ProjectParams)
|
|
||||||
|
|
||||||
async function addProject (projectData: ProjectParams) {
|
async function addProject (projectData: ProjectParams) {
|
||||||
const newDataProject = await projectsStore.add(projectData)
|
const newDataProject = await projectsStore.add(projectData)
|
||||||
await router.replace({ name: 'chats', params: { id: newDataProject.id }})
|
await router.replace({ name: 'chats', params: { id: newDataProject.id }})
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<project-block
|
<project-block
|
||||||
v-if="project"
|
:project
|
||||||
v-model="project"
|
|
||||||
title="project_edit__title_card"
|
title="project_edit__title_card"
|
||||||
btnText="project_edit__btn"
|
btnText="project_edit__btn"
|
||||||
@update="updateProject"
|
@update="updateProject"
|
||||||
@@ -9,7 +8,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import projectBlock from 'components/projectBlock.vue'
|
import projectBlock from 'components/projectBlock.vue'
|
||||||
import { useProjectsStore } from 'stores/projects'
|
import { useProjectsStore } from 'stores/projects'
|
||||||
@@ -18,27 +17,14 @@
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const projectsStore = useProjectsStore()
|
const projectsStore = useProjectsStore()
|
||||||
|
|
||||||
const project = ref<ProjectParams | null>(null)
|
|
||||||
const projectId = computed(() => projectsStore.currentProjectId)
|
const projectId = computed(() => projectsStore.currentProjectId)
|
||||||
|
|
||||||
|
const project = projectId.value && projectsStore.projectById(projectId.value) || {} as ProjectParams
|
||||||
if (projectsStore.isInit) {
|
|
||||||
project.value = projectId.value
|
|
||||||
? { ...projectsStore.projectById(projectId.value) } as ProjectParams
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => projectsStore.isInit, (isInit) => {
|
|
||||||
if (isInit && projectId.value && !project.value) {
|
|
||||||
project.value = { ...projectsStore.projectById(projectId.value) as ProjectParams }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function updateProject (projectData: ProjectParams) {
|
async function updateProject (projectData: ProjectParams) {
|
||||||
if (projectId.value) {
|
if (!projectId .value || !project) return
|
||||||
await projectsStore.update(projectId.value, projectData)
|
await projectsStore.update(projectId.value, projectData)
|
||||||
router.go(-1)
|
router.go(-1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -75,8 +75,8 @@
|
|||||||
const usersStore = useUsersStore()
|
const usersStore = useUsersStore()
|
||||||
const companiesStore = useCompaniesStore()
|
const companiesStore = useCompaniesStore()
|
||||||
|
|
||||||
const chatsQty = computed(() => chatsStore.getChats.length)
|
const chatsQty = computed(() => chatsStore.chats.length)
|
||||||
const usersQty = computed(() => usersStore.getUsers.length)
|
const usersQty = computed(() => usersStore.users.length)
|
||||||
const companiesQty = computed(() => companiesStore.getCompanies.length)
|
const companiesQty = computed(() => companiesStore.getCompanies.length)
|
||||||
|
|
||||||
const tabs = ref([
|
const tabs = ref([
|
||||||
|
|||||||
@@ -21,12 +21,12 @@
|
|||||||
v-if="projects.length!==0 || archiveProjects.length!==0"
|
v-if="projects.length!==0 || archiveProjects.length!==0"
|
||||||
>
|
>
|
||||||
<q-input
|
<q-input
|
||||||
v-if="projects.length!== 0"
|
v-if="projects.length!==0"
|
||||||
v-model="searchProject"
|
v-model="searchProject"
|
||||||
clearable
|
clearable
|
||||||
clear-icon="close"
|
clear-icon="close"
|
||||||
:placeholder="$t('projects__search')"
|
:placeholder="$t('projects__search')"
|
||||||
dense
|
dense filled
|
||||||
class="col-grow q-px-md q-py-md"
|
class="col-grow q-px-md q-py-md"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
@@ -61,16 +61,16 @@
|
|||||||
/>
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label lines="1" class="text-bold">{{ item.name }}</q-item-label>
|
<q-item-label lines="1" class="text-bold">
|
||||||
|
{{ item.name }}
|
||||||
|
</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"
|
||||||
>
|
>
|
||||||
{{ item.description }}
|
{{ item.description }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
<q-item-label lines="1" class="text-caption text-grey">
|
||||||
<q-item-section side top>
|
|
||||||
<q-item-label lines="1" class="text-caption">
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<q-icon name="mdi-chat-outline"/>
|
<q-icon name="mdi-chat-outline"/>
|
||||||
@@ -82,10 +82,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
<q-item-label>
|
<q-item-label>
|
||||||
<q-btn
|
<q-btn
|
||||||
color="grey"
|
color="grey"
|
||||||
size="sm"
|
size="md"
|
||||||
dense flat round
|
dense flat round
|
||||||
icon="mdi-pencil"
|
icon="mdi-pencil"
|
||||||
class="q-ml-sm"
|
class="q-ml-sm"
|
||||||
@@ -225,7 +227,7 @@
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const projectsStore = useProjectsStore()
|
const projectsStore = useProjectsStore()
|
||||||
const projects = projectsStore.getProjects
|
const projects = computed(() => projectsStore.projects)
|
||||||
const projectsInit = computed(() => projectsStore.isInit)
|
const projectsInit = computed(() => projectsStore.isInit)
|
||||||
|
|
||||||
const searchProject = ref('')
|
const searchProject = ref('')
|
||||||
@@ -259,9 +261,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const displayProjects = computed(() => {
|
const displayProjects = computed(() => {
|
||||||
if (!searchProject.value || !(searchProject.value && searchProject.value.trim())) return projects
|
if (!searchProject.value || !(searchProject.value && searchProject.value.trim())) return projects.value
|
||||||
const searchChatValue = searchProject.value.trim().toLowerCase()
|
const searchChatValue = searchProject.value.trim().toLowerCase()
|
||||||
const arrOut = projects
|
const arrOut = projects.value
|
||||||
.filter((el: Project) =>
|
.filter((el: Project) =>
|
||||||
el.name.toLowerCase().includes(searchChatValue) ||
|
el.name.toLowerCase().includes(searchChatValue) ||
|
||||||
el.description && el.description.toLowerCase().includes(searchProject.value)
|
el.description && el.description.toLowerCase().includes(searchProject.value)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<user-block
|
<user-block
|
||||||
v-if="user"
|
:user
|
||||||
v-model="user"
|
|
||||||
title="user_edit__title_card"
|
title="user_edit__title_card"
|
||||||
btnText="user_edit__btn"
|
btnText="user_edit__btn"
|
||||||
@update="updateUser"
|
@update="updateUser"
|
||||||
@@ -9,38 +8,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import userBlock from 'components/userBlock.vue'
|
import userBlock from 'components/userBlock.vue'
|
||||||
import { useUsersStore } from 'stores/users'
|
import { useUsersStore } from 'stores/users'
|
||||||
import { parseIntString } from 'src/utils/helpers'
|
import { parseIntString } from 'utils/helpers'
|
||||||
import type { User } from 'types/User'
|
import type { User } from 'types/User'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const usersStore = useUsersStore()
|
const usersStore = useUsersStore()
|
||||||
|
|
||||||
const user = ref<User | null>(null)
|
|
||||||
const userId = computed(() => parseIntString(route.params.userId))
|
const userId = computed(() => parseIntString(route.params.userId))
|
||||||
|
|
||||||
function initUser() {
|
const user = userId.value && usersStore.userById(userId.value) || {} as User
|
||||||
if (usersStore.isInit && userId.value) {
|
|
||||||
const foundUser = usersStore.userById(userId.value)
|
|
||||||
if (foundUser) {
|
|
||||||
user.value = { ...foundUser } as User
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usersStore.isInit) initUser()
|
async function updateUser (user: User) {
|
||||||
|
if (!userId .value || !user) return
|
||||||
watch(() => usersStore.isInit, initUser)
|
await usersStore.update(userId.value, user)
|
||||||
|
router.go(-1)
|
||||||
async function updateUser (userData: User) {
|
|
||||||
if (userId.value) {
|
|
||||||
await usersStore.update(userId.value, userData)
|
|
||||||
router.go(-1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,107 +8,177 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<pn-scroll-list >
|
<pn-scroll-list >
|
||||||
<div class="q-px-md">
|
<template #card-body-header>
|
||||||
<div
|
<div
|
||||||
id="subscribe-current-balance"
|
id="subscribe-current-balance"
|
||||||
class="flex w100 q-px-md q-py-sm row"
|
class="flex w100 q-px-md q-py-md row"
|
||||||
style="border-radius: var(--top-raduis); border: 1px solid var(--q-primary);"
|
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="flex w100 justify-between items-center no-wrap">
|
<div class="flex w100 justify-between items-center no-wrap">
|
||||||
<div class="flex no-wrap items-center text-h6 col-9">
|
<div class="flex items-center text-grey">
|
||||||
{{ $t('subscribe__current_plan') }}
|
{{ $t('subscribe__current_plan') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center column col-3">
|
<div class="flex items-center column text-right">
|
||||||
<span class="text-bold">
|
<span class="text-bold">
|
||||||
{{ currentPlanData?.name }}
|
{{ currentPlanData?.name }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-caption" style="line-height: 0.5em;">
|
|
||||||
{{ $t('subscribe__plan_exp') }}
|
|
||||||
{{ date.formatDate(currentPlanData.exp * 1000, 'DD.MM.YYYY') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex row w100 ow-wrap items-center text-caption q-pt-sm">
|
<div
|
||||||
<div class="col-9">{{ $t('subscribe__plan_active_chats') }}</div>
|
class="row w100 no-wrap items-center text-caption justify-between"
|
||||||
<div class="col-3" align="center">
|
v-if="currentPlanData.name !== 'TEST'"
|
||||||
<span class="text-brand2 text-bold q-pr-xs">{{ currentPlanData?.active_chats }}</span>
|
>
|
||||||
<span class="text-grey">/{{ currentPlanData?.chatsQty }}</span>
|
<div class="text-grey text-no-wrap ellipsis">
|
||||||
|
{{ $t('subscribe__plan_date') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<span class="text-no-wrap text-right">
|
||||||
|
{{ date.formatDate(currentPlanData.exp * 1000, 'DD.MM.YYYY') }}
|
||||||
</div>
|
|
||||||
<div class="flex w100 justify-center text-grey q-pt-md q-pb-none">{{ $t('subscribe__plans')}}</div>
|
|
||||||
<q-list separator>
|
|
||||||
<q-item
|
|
||||||
v-for="item in plans"
|
|
||||||
:key="item.id"
|
|
||||||
>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-radio v-model="newPlan" :val="item" v-if="item.name!=='TEST'"/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-bold">{{ item.name }}</q-item-label>
|
|
||||||
<div class="flex no-wrap items-center text-caption">
|
|
||||||
<span v-if="item.chatsQty" class="q-pr-xs">
|
|
||||||
{{ $t('subscribe__chats_max') }}
|
|
||||||
</span>
|
|
||||||
<span v-if="item.chatsQty">
|
|
||||||
{{ item.chatsQty }}
|
|
||||||
</span>
|
|
||||||
<q-icon v-else name="mdi-all-inclusive" size="sm"/>
|
|
||||||
<span class="q-pl-xs">
|
|
||||||
{{ $t('subscribe__chats')}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section side>
|
|
||||||
<div v-if="item.price" class="flex column items-center">
|
|
||||||
<div class="flex no-wrap items-center">
|
|
||||||
<telegram-star color="gold" size="18px" class="q-mr-xs"/>
|
|
||||||
<span class="text-h6">{{ formatNumber(item.price) }}</span>
|
|
||||||
<span class="text-caption q-pl-xs">{{ $t('subscribe__per_month') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="text-bold">
|
|
||||||
{{ $t('subscribe__free_tax') }}
|
|
||||||
</div>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
<div class="flex w100 justify-center text-caption text-grey q-pt-sm text-center">
|
|
||||||
{{ $t('subscribe__plans_description') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-btn-group spread flat class="w100 q-py-sm">
|
|
||||||
<q-btn
|
|
||||||
v-for="period in periods"
|
|
||||||
:key="period.id"
|
|
||||||
:color="selectPeriod === period.value ? 'primary' : 'white'"
|
|
||||||
:text-color="selectPeriod === period.value ? 'white' : 'primary'"
|
|
||||||
@click="selectPeriod = period.value"
|
|
||||||
no-caps
|
|
||||||
>
|
|
||||||
<div class="column items-center w100 self-end">
|
|
||||||
<q-badge v-show="period.sale" color="red">{{ period.sale }}% off</q-badge>
|
|
||||||
<span>{{ $t(period.name) }}</span>
|
|
||||||
<span class="text-caption">({{ $t(period.name_in_days) }})</span>
|
|
||||||
</div>
|
|
||||||
</q-btn>
|
|
||||||
</q-btn-group>
|
|
||||||
|
|
||||||
<div class="text-caption column text-grey">
|
|
||||||
<span>{{ $t('subscribe__plans_period_notes') + ' ' }}</span>
|
|
||||||
<span
|
|
||||||
@click="router.push({ name: 'change_plan_rules' })"
|
|
||||||
class="text-info cursor-pointer"
|
|
||||||
>
|
|
||||||
{{ $t('subscribe__plans_period_notes_more_info') }}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row w100 no-wrap items-center text-caption text-grey justify-between">
|
||||||
|
<div>
|
||||||
|
{{ $t('subscribe__plan_active_chats') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<span class="text-brand2 text-bold q-pr-xs">{{ currentPlanData?.active_chats }}</span>
|
||||||
|
<span>/{{ currentPlanData?.chatsQty }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="w100 column items-center text-center text-grey q-px-md">
|
||||||
|
{{ $t('subscribe__plans')}}
|
||||||
|
<div class="text-caption">
|
||||||
|
{{ $t('subscribe__plans_description') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-list separator>
|
||||||
|
<q-item
|
||||||
|
v-for="item in plans"
|
||||||
|
:key="item.id"
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
tag="label"
|
||||||
|
:disable="item.name === 'TEST' && currentPlanData.name !== 'TEST'"
|
||||||
|
>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-radio
|
||||||
|
v-model="newPlan"
|
||||||
|
:val="item"
|
||||||
|
:disable="item.name === 'TEST' && currentPlanData.name != 'TEST'"
|
||||||
|
:keep-color="item.name === 'TEST'&& currentPlanData.name !== 'TEST'"
|
||||||
|
:color="item.name === 'TEST' && currentPlanData.name !== 'TEST' ? 'grey-5' : ''"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<div class="column">
|
||||||
|
<span class="text-bold">
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
|
<div class="flex no-wrap items-center text-caption" style="line-height: 1em;">
|
||||||
|
<span v-if="item.chatsQty">
|
||||||
|
{{ $t('subscribe__chats_max') + ' ' + item.chatsQty }}
|
||||||
|
</span>
|
||||||
|
<q-icon v-else name="mdi-all-inclusive" size="sm"/>
|
||||||
|
<span class="q-pl-xs">
|
||||||
|
{{ $t('subscribe__chats')}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-if="item.name === 'TEST' && currentPlanData.name !== 'TEST'"
|
||||||
|
class="text-caption text-grey-7"
|
||||||
|
>
|
||||||
|
{{ $t('subscribe__TEST_via_support')}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section
|
||||||
|
class="text-left"
|
||||||
|
:style="{ width: `${maxWidthStarsSection}px` }"
|
||||||
|
>
|
||||||
|
<q-resize-observer @resize="updateMaxWidthStarsSection" />
|
||||||
|
<div v-if="item.price" class="flex column justify-start">
|
||||||
|
<div class="flex no-wrap items-center">
|
||||||
|
<telegram-star color="gold" size="18px" class="q-mr-xs"/>
|
||||||
|
<span class="text-h6">{{ formatNumber(item.price) }}</span>
|
||||||
|
<span class="text-caption q-pl-xs">{{ $t('subscribe__per_month') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-bold self-center">
|
||||||
|
{{ $t('subscribe__free_tax') }}
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
|
||||||
|
<div class="q-px-md">
|
||||||
|
<div class="w100 text-grey text-center q-pt-sm">
|
||||||
|
{{ $t('subscribe__plans_interval') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-btn-group spread flat class="w100 q-py-sm">
|
||||||
|
<q-btn
|
||||||
|
v-for="period in periods"
|
||||||
|
:key="period.id"
|
||||||
|
:outline="selectPeriod === period.value"
|
||||||
|
@click="selectPeriod = period.value"
|
||||||
|
:color="selectPeriod === period.value ? 'primary' : ''"
|
||||||
|
no-caps dense
|
||||||
|
>
|
||||||
|
<div class="column items-center w100 self-end">
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="text-h6"
|
||||||
|
:class="selectPeriod === period.value ? 'text-primary' : 'text-black'"
|
||||||
|
>
|
||||||
|
{{ $t(period.name) }}
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="row no-wrap w100 q-px-xs"
|
||||||
|
:class="period.sale ? 'justify-between' : 'justify-center'"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="text-caption text-grey"
|
||||||
|
>
|
||||||
|
{{ $t(period.name_in_days) }}
|
||||||
|
</span>
|
||||||
|
<q-badge v-show="period.sale" color="red">-{{ period.sale }}%</q-badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-btn>
|
||||||
|
</q-btn-group>
|
||||||
|
|
||||||
|
<div class="text-caption text-grey">
|
||||||
|
<span>{{ $t('subscribe__plans_period_notes') + ' ' }}</span>
|
||||||
|
<span
|
||||||
|
@click="router.push({ name: 'change_plan_rules' })"
|
||||||
|
class="text-info cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ $t('subscribe__plans_period_notes_more_info') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
v-model="promocode"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
:label="$t('subscribe__promocode')"
|
||||||
|
class="q-pt-md"
|
||||||
|
>
|
||||||
|
<template #append v-if="promocode !== ''">
|
||||||
|
<q-btn
|
||||||
|
round dense flat
|
||||||
|
color="primary"
|
||||||
|
icon="mdi-arrow-right-circle-outline"
|
||||||
|
@click="sendPromocode"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
</pn-scroll-list>
|
</pn-scroll-list>
|
||||||
</pn-page-card>
|
</pn-page-card>
|
||||||
@@ -121,12 +191,12 @@
|
|||||||
import { date } from 'quasar'
|
import { date } from 'quasar'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import type { WebApp } from '@twa-dev/types'
|
import type { WebApp } from '@twa-dev/types'
|
||||||
import { colors } from 'quasar';
|
import { colors } from 'quasar'
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const tg = inject('tg') as WebApp
|
const tg = inject('tg') as WebApp
|
||||||
tg.MainButton.show()
|
|
||||||
|
|
||||||
// @ts-expect-error: get hex text
|
// @ts-expect-error: get hex text
|
||||||
tg.MainButton.color = colors.getPaletteColor('primary')
|
tg.MainButton.color = colors.getPaletteColor('primary')
|
||||||
@@ -146,7 +216,7 @@
|
|||||||
|
|
||||||
const selectPeriod = ref(periods[1]?.value)
|
const selectPeriod = ref(periods[1]?.value)
|
||||||
|
|
||||||
const newPlan = ref(plans[1])
|
const newPlan = ref(plans[0])
|
||||||
|
|
||||||
interface CurrentPlan {
|
interface CurrentPlan {
|
||||||
plan: string
|
plan: string
|
||||||
@@ -155,7 +225,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentPlan = ref<CurrentPlan>({ // temp, this get from api
|
const currentPlan = ref<CurrentPlan>({ // temp, this get from api
|
||||||
plan: plans[0].val,
|
plan: plans[1].val,
|
||||||
active_chats: 20,
|
active_chats: 20,
|
||||||
exp: Date.now() / 1000 + 500000
|
exp: Date.now() / 1000 + 500000
|
||||||
})
|
})
|
||||||
@@ -186,12 +256,12 @@
|
|||||||
)
|
)
|
||||||
const newDateExp = date.addToDate(Date.now(), { months: Number(selectPeriod.value) })
|
const newDateExp = date.addToDate(Date.now(), { months: Number(selectPeriod.value) })
|
||||||
|
|
||||||
return t('subscribe__pay') + ' ⭐' + stars +
|
return t(newPlan.value.id !== currentPlanData.value.id ? 'subscribe__pay' : 'subscribe__pay_renew') +
|
||||||
|
' ⭐' + stars +
|
||||||
' (' + t('subscribe__plan_exp') + ' ' +
|
' (' + t('subscribe__plan_exp') + ' ' +
|
||||||
date.formatDate(newDateExp, 'DD.MM.YYYY') + ')'
|
date.formatDate(newDateExp, 'DD.MM.YYYY') + ')'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
function formatNumber (number: string | number) {
|
function formatNumber (number: string | number) {
|
||||||
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ")
|
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ")
|
||||||
}
|
}
|
||||||
@@ -201,6 +271,43 @@
|
|||||||
watch(textBtn, () => tg.MainButton.setText(textBtn.value),
|
watch(textBtn, () => tg.MainButton.setText(textBtn.value),
|
||||||
{ immediate: true })
|
{ immediate: true })
|
||||||
|
|
||||||
|
watch(newPlan, () =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||||
|
{ newPlan.value.name === 'TEST' ? tg.MainButton.hide() : tg.MainButton.show() },
|
||||||
|
{ immediate: true })
|
||||||
|
|
||||||
|
const maxWidthStarsSection = ref(0)
|
||||||
|
|
||||||
|
interface sizeParams {
|
||||||
|
height: number,
|
||||||
|
width: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMaxWidthStarsSection (size: sizeParams) {
|
||||||
|
if (size.width > maxWidthStarsSection.value) {
|
||||||
|
maxWidthStarsSection.value = size.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const promocode = ref('')
|
||||||
|
|
||||||
|
function sendPromocode () {
|
||||||
|
promocode.value = ''
|
||||||
|
showNotify('fail')
|
||||||
|
}
|
||||||
|
|
||||||
|
const $q = useQuasar()
|
||||||
|
|
||||||
|
const showNotify = (message: 'success' | 'fail') => {
|
||||||
|
$q.notify({
|
||||||
|
message: t(message === 'success' ? 'subscribe__promocode_success' : 'subscribe__promocode_fail'),
|
||||||
|
type: message === 'success' ? 'positive' : 'negative',
|
||||||
|
position: 'bottom',
|
||||||
|
timeout: 1000,
|
||||||
|
multiLine: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
<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>
|
<template #card-body-header>
|
||||||
|
|
||||||
<div class="flex row q-ma-md justify-between" v-if="chats.length !== 0">
|
<div class="flex row q-ma-md justify-between" v-if="chats.length !== 0">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="search"
|
v-model="search"
|
||||||
clearable
|
clearable
|
||||||
clear-icon="close"
|
clear-icon="close"
|
||||||
:placeholder="$t('chats__search')"
|
:placeholder="$t('chats__search')"
|
||||||
dense
|
dense filled
|
||||||
class="col-grow"
|
class="col-grow"
|
||||||
v-if="chats.length !== 0"
|
v-if="chats.length !== 0"
|
||||||
>
|
>
|
||||||
@@ -52,18 +51,15 @@
|
|||||||
{{ item.description }}
|
{{ item.description }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label caption lines="1">
|
<q-item-label caption lines="1">
|
||||||
<div class = "flex justify-start items-center no-wrap">
|
|
||||||
<div class="q-mr-sm flex items-center no-wrap">
|
|
||||||
<q-icon name="mdi-account-multiple-outline" class="q-mr-xs"/>
|
|
||||||
<span>{{ item.user_count }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="q-mx-sm flex items-center no-wrap ellipsis" v-if="item.owner_id">
|
|
||||||
<q-icon name="mdi-key" class="q-mr-xs"/>
|
|
||||||
<span class="ellipsis">{{ usersStore.userNameById(item.owner_id) }} </span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<div class="flex items-center no-wrap text-caption">
|
||||||
|
<q-icon name="mdi-account-outline"/>
|
||||||
|
<span>{{ item.user_count }}</span>
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-slide-item>
|
</q-slide-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
@@ -166,7 +162,6 @@
|
|||||||
<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 { useUsersStore } from 'stores/users'
|
|
||||||
import type { WebApp } from '@twa-dev/types'
|
import type { WebApp } from '@twa-dev/types'
|
||||||
import { useI18n } from "vue-i18n"
|
import { useI18n } from "vue-i18n"
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
@@ -179,7 +174,6 @@
|
|||||||
const search = ref('')
|
const search = ref('')
|
||||||
const showOverlay = ref<boolean>(false)
|
const showOverlay = ref<boolean>(false)
|
||||||
const chatsStore = useChatsStore()
|
const chatsStore = useChatsStore()
|
||||||
const usersStore = useUsersStore()
|
|
||||||
const showDialogDeleteChat = ref<boolean>(false)
|
const showDialogDeleteChat = ref<boolean>(false)
|
||||||
const deleteChatId = ref<number | undefined>(undefined)
|
const deleteChatId = ref<number | undefined>(undefined)
|
||||||
const currentSlideEvent = ref<SlideEvent | null>(null)
|
const currentSlideEvent = ref<SlideEvent | null>(null)
|
||||||
@@ -192,7 +186,7 @@
|
|||||||
reset: () => void
|
reset: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const chats = chatsStore.getChats
|
const chats = computed(() => chatsStore.chats)
|
||||||
const chatsInit = computed(() => chatsStore.isInit)
|
const chatsInit = computed(() => chatsStore.isInit)
|
||||||
|
|
||||||
const fabMenu = [
|
const fabMenu = [
|
||||||
@@ -201,9 +195,9 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
const displayChats = computed(() => {
|
const displayChats = computed(() => {
|
||||||
if (!search.value || !(search.value && search.value.trim())) return chats
|
if (!search.value || !(search.value && search.value.trim())) return chats.value
|
||||||
const searchValue = search.value.trim().toLowerCase()
|
const searchValue = search.value.trim().toLowerCase()
|
||||||
const arrOut = chats
|
const arrOut = chats.value
|
||||||
.filter(el =>
|
.filter(el =>
|
||||||
el.name.toLowerCase().includes(searchValue) ||
|
el.name.toLowerCase().includes(searchValue) ||
|
||||||
el.description && el.description.toLowerCase().includes(searchValue)
|
el.description && el.description.toLowerCase().includes(searchValue)
|
||||||
|
|||||||
@@ -1,29 +1,27 @@
|
|||||||
<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>
|
<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'"
|
flat
|
||||||
flat
|
no-caps
|
||||||
no-caps
|
@click="maskCompany"
|
||||||
@click="maskCompany"
|
:disable="companies.length <= 2"
|
||||||
:disable="companies.length <= 2"
|
class="q-pr-md"
|
||||||
class="q-pr-md"
|
rounded
|
||||||
rounded
|
>
|
||||||
>
|
<q-icon
|
||||||
<q-icon
|
left
|
||||||
left
|
size="sm"
|
||||||
size="sm"
|
name="mdi-domino-mask"
|
||||||
name="mdi-domino-mask"
|
class="q-mr-xs"
|
||||||
class="q-mr-xs"
|
/>
|
||||||
/>
|
<div>
|
||||||
<div>
|
{{ $t('companies__mask')}}
|
||||||
{{ $t('companies__mask')}}
|
</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
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="project-info"
|
id="project-info"
|
||||||
|
v-if="project"
|
||||||
:style="{ height: headerHeight + 'px', minHeight: '48px' }"
|
:style="{ height: headerHeight + 'px', minHeight: '48px' }"
|
||||||
class="flex row items-center justify-between no-wrap w100 q-gutter-x-sm"
|
class="flex row items-center justify-between no-wrap w100 q-gutter-x-sm"
|
||||||
style="overflow: hidden; transition: height 0.3s ease-in-out; margin-left: 0"
|
style="overflow: hidden; transition: height 0.3s ease-in-out; margin-left: 0"
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
size="lg"
|
size="lg"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex column text-white fit">
|
<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"
|
||||||
>
|
>
|
||||||
@@ -85,18 +86,15 @@
|
|||||||
return currentProject
|
return currentProject
|
||||||
? {
|
? {
|
||||||
name: currentProject.name,
|
name: currentProject.name,
|
||||||
description: currentProject.description ?? '',
|
description: currentProject.description,
|
||||||
logo: currentProject.logo ?? ''
|
logo: currentProject.logo
|
||||||
}
|
|
||||||
: {
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
logo: ''
|
|
||||||
}
|
}
|
||||||
|
: null
|
||||||
})
|
})
|
||||||
|
|
||||||
function toggleExpand () {
|
function toggleExpand () {
|
||||||
expandProjectInfo.value = !expandProjectInfo.value
|
if (project.value && (project.value.description || project.value.logo))
|
||||||
|
expandProjectInfo.value = !expandProjectInfo.value
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toProjects () {
|
async function toProjects () {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
clearable
|
clearable
|
||||||
clear-icon="close"
|
clear-icon="close"
|
||||||
:placeholder="$t('users__search')"
|
:placeholder="$t('users__search')"
|
||||||
dense
|
dense filled
|
||||||
class="col-grow"
|
class="col-grow"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
@@ -254,7 +254,7 @@
|
|||||||
const usersStore = useUsersStore()
|
const usersStore = useUsersStore()
|
||||||
const companiesStore = useCompaniesStore()
|
const companiesStore = useCompaniesStore()
|
||||||
|
|
||||||
const users = usersStore.getUsers
|
const users = computed(() => usersStore.users)
|
||||||
const usersInit = computed(() => usersStore.isInit)
|
const usersInit = computed(() => usersStore.isInit)
|
||||||
|
|
||||||
const blockUserId = ref<number | undefined>(undefined)
|
const blockUserId = ref<number | undefined>(undefined)
|
||||||
@@ -263,7 +263,7 @@
|
|||||||
const closedByUserAction = ref(false)
|
const closedByUserAction = ref(false)
|
||||||
const { userSection } = useUserSection()
|
const { userSection } = useUserSection()
|
||||||
|
|
||||||
const mapUsers = computed(() => users.map(el => ({
|
const mapUsers = computed(() => users.value.map(el => ({
|
||||||
...el,
|
...el,
|
||||||
...userSection(el),
|
...userSection(el),
|
||||||
companyName: el.company_id && companiesStore.companyById(el.company_id)
|
companyName: el.company_id && companiesStore.companyById(el.company_id)
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export default defineRouter(function (/* { store, ssrContext } */) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (to.name === 'telegram_only') return true
|
if (to.name === 'telegram_only') return true
|
||||||
|
if (typeof to.name === 'string' && publicPath.includes(to.name)) return true
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const projectsStore = useProjectsStore()
|
const projectsStore = useProjectsStore()
|
||||||
@@ -48,8 +49,6 @@ export default defineRouter(function (/* { store, ssrContext } */) {
|
|||||||
if (to.name === 'login')
|
if (to.name === 'login')
|
||||||
return authStore.isAuth ? { name: 'projects', replace: true } : true
|
return authStore.isAuth ? { name: 'projects', replace: true } : true
|
||||||
|
|
||||||
if (typeof to.name === 'string' && publicPath.includes(to.name)) return true
|
|
||||||
|
|
||||||
if (to.name === 'agreements') return true
|
if (to.name === 'agreements') return true
|
||||||
|
|
||||||
if (to.name === 'create_account' && !authStore.isAuth) return true
|
if (to.name === 'create_account' && !authStore.isAuth) return true
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
name: 'agreements',
|
name: 'agreements',
|
||||||
path: '/agreements',
|
path: '/agreements',
|
||||||
component: () => import('src/pages/AgreementsPage.vue')
|
component: () => import('pages/AgreementsPage.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'terms',
|
name: 'terms',
|
||||||
@@ -145,7 +145,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
name: 'support',
|
name: 'support',
|
||||||
path: '/support',
|
path: '/support',
|
||||||
component: () => import('src/pages/account/SupportPage.vue')
|
component: () => import('pages/account/SupportPage.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'your_company',
|
name: 'your_company',
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ interface Customer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface WSMessage {
|
interface WSMessage {
|
||||||
id: number
|
id?: number
|
||||||
action: string
|
action?: string
|
||||||
entity: string
|
entity?: string
|
||||||
entity_id?: number
|
entity_id?: number
|
||||||
needUpdateStores: string[]
|
needUpdateStores: string[]
|
||||||
}
|
}
|
||||||
@@ -53,11 +53,17 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
socket.onmessage = (event) => {
|
||||||
if (wsEvent.value) {
|
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.needUpdateStores = []
|
||||||
wsEvent.value = JSON.parse(event.data)
|
wsEvent.value = JSON.parse(event.data)
|
||||||
if (wsEvent.value?.entity === 'chat') wsEvent.value.needUpdateStores = ['users']
|
if (wsEvent.value?.entity === 'chat') wsEvent.value.needUpdateStores = ['users']
|
||||||
}
|
} */
|
||||||
|
|
||||||
socket.onerror = (event) => console.error("Ошибка ", event)
|
socket.onerror = (event) => console.error("Ошибка ", event)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,15 +42,13 @@ export const useChatsStore = defineStore('chats', () => {
|
|||||||
await api.get('/project/' + currentProjectId.value + '/chat/' + chatId)
|
await api.get('/project/' + currentProjectId.value + '/chat/' + chatId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getChats = computed(() => chats.value)
|
|
||||||
|
|
||||||
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()
|
const authStore = useAuthStore()
|
||||||
watch(() => authStore.wsEvent, async (event) => {
|
watch(() => authStore.wsEvent, async (event) => {
|
||||||
if (!event || event.entity !== 'chat' || !event.needUpdateStores.includes('chats')) return
|
if (!event || !event.needUpdateStores.includes('chats')) return
|
||||||
await init()
|
await init()
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
@@ -61,7 +59,6 @@ export const useChatsStore = defineStore('chats', () => {
|
|||||||
reset,
|
reset,
|
||||||
unlink,
|
unlink,
|
||||||
getKey,
|
getKey,
|
||||||
getChats,
|
|
||||||
chatById,
|
chatById,
|
||||||
getChatUsers
|
getChatUsers
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ref, watch, computed } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { api } from 'boot/axios'
|
import { api } from 'boot/axios'
|
||||||
|
|
||||||
@@ -81,8 +81,6 @@ export const useProjectsStore = defineStore('projects', () => {
|
|||||||
companiesStore.reset()
|
companiesStore.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProjects = computed(() => projects.value)
|
|
||||||
|
|
||||||
watch (currentProjectId, async (newId) => {
|
watch (currentProjectId, async (newId) => {
|
||||||
if (newId) {
|
if (newId) {
|
||||||
await initStores()
|
await initStores()
|
||||||
@@ -104,7 +102,6 @@ export const useProjectsStore = defineStore('projects', () => {
|
|||||||
projectById,
|
projectById,
|
||||||
setCurrentProjectId,
|
setCurrentProjectId,
|
||||||
initStores,
|
initStores,
|
||||||
resetStores,
|
resetStores
|
||||||
getProjects
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -58,11 +58,9 @@ export const useUsersStore = defineStore('users', () => {
|
|||||||
|| '---'
|
|| '---'
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUsers = computed(() => users.value)
|
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
watch(() => authStore.wsEvent, async (event) => {
|
watch(() => authStore.wsEvent, async (event) => {
|
||||||
if (!event || event.entity !== 'user' || !event.needUpdateStores.includes('users')) return
|
if (!event || !event.needUpdateStores.includes('users')) return
|
||||||
await init()
|
await init()
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
@@ -75,7 +73,6 @@ export const useUsersStore = defineStore('users', () => {
|
|||||||
blockUser,
|
blockUser,
|
||||||
unblockUser,
|
unblockUser,
|
||||||
userById,
|
userById,
|
||||||
userNameById,
|
userNameById
|
||||||
getUsers
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
interface Chat {
|
interface Chat {
|
||||||
id: number
|
id: number
|
||||||
project_id: number
|
project_id: number
|
||||||
telegram_id: number
|
telegram_id: string
|
||||||
|
message_id: number
|
||||||
name: string
|
name: string
|
||||||
is_channel: boolean
|
is_channel: boolean
|
||||||
bot_can_ban: boolean
|
bot_can_ban: boolean
|
||||||
@@ -10,7 +11,6 @@ interface Chat {
|
|||||||
description: string | null
|
description: string | null
|
||||||
logo: string | null
|
logo: string | null
|
||||||
owner_id?: number
|
owner_id?: number
|
||||||
invite_link: string
|
|
||||||
users: number []
|
users: number []
|
||||||
[key: string]: number | string | boolean | null | number[]
|
[key: string]: number | string | boolean | null | number[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,113 +1,15 @@
|
|||||||
function isDirty(
|
|
||||||
obj1: Record<string, unknown> | null | undefined,
|
|
||||||
obj2: Record<string, unknown> | null | undefined
|
|
||||||
): boolean {
|
|
||||||
const actualObj1 = obj1 ?? {}
|
|
||||||
const actualObj2 = obj2 ?? {}
|
|
||||||
|
|
||||||
const filteredObj1 = filterIgnored(actualObj1)
|
|
||||||
const filteredObj2 = filterIgnored(actualObj2)
|
|
||||||
|
|
||||||
const allKeys = new Set([...Object.keys(filteredObj1), ...Object.keys(filteredObj2)])
|
|
||||||
|
|
||||||
for (const key of allKeys) {
|
|
||||||
const hasKey1 = Object.hasOwn(filteredObj1, key)
|
|
||||||
const hasKey2 = Object.hasOwn(filteredObj2, key)
|
|
||||||
|
|
||||||
// Различие в наличии ключа
|
|
||||||
if (hasKey1 !== hasKey2) return true
|
|
||||||
|
|
||||||
if (hasKey1 && hasKey2) {
|
|
||||||
const val1 = filteredObj1[key]
|
|
||||||
const val2 = filteredObj2[key]
|
|
||||||
|
|
||||||
// Сравнение массивов
|
|
||||||
if (Array.isArray(val1) && Array.isArray(val2)) {
|
|
||||||
if (val1.length !== val2.length) return true
|
|
||||||
const set2 = new Set(val2)
|
|
||||||
if (!val1.every(item => set2.has(item))) return true
|
|
||||||
}
|
|
||||||
// Один массив, другой - нет
|
|
||||||
else if (Array.isArray(val1) || Array.isArray(val2)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Сравнение строк
|
|
||||||
else if (typeof val1 === 'string' && typeof val2 === 'string') {
|
|
||||||
if (val1.trim() !== val2.trim()) return true
|
|
||||||
}
|
|
||||||
// Сравнение примитивов
|
|
||||||
else if (val1 !== val2) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterIgnored(obj: Record<string, unknown>): Record<string, string | number | boolean | (string | number)[]> {
|
|
||||||
const filtered: Record<string, string | number | boolean | (string | number)[]> = {}
|
|
||||||
|
|
||||||
for (const key in obj) {
|
|
||||||
const value = obj[key]
|
|
||||||
|
|
||||||
// Обработка массивов
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
// Отбрасываем пустые массивы
|
|
||||||
if (value.length === 0) continue
|
|
||||||
|
|
||||||
// Фильтруем массивы с некорректными элементами
|
|
||||||
if (value.every(item =>
|
|
||||||
typeof item === 'string' ||
|
|
||||||
typeof item === 'number'
|
|
||||||
)) {
|
|
||||||
filtered[key] = value
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработка примитивов
|
|
||||||
if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработка строк
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
const trimmed = value.trim()
|
|
||||||
if (trimmed === '') continue
|
|
||||||
filtered[key] = trimmed
|
|
||||||
}
|
|
||||||
// Обработка чисел и boolean
|
|
||||||
else if (value !== 0 && value !== false) {
|
|
||||||
filtered[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseIntString (s: string | string[] | undefined) :number | null {
|
function parseIntString (s: string | string[] | undefined) :number | null {
|
||||||
if (typeof s !== 'string') return null
|
if (typeof s !== 'string') return null
|
||||||
const regex = /^[+-]?\d+$/
|
const regex = /^[+-]?\d+$/
|
||||||
return regex.test(s) ? Number(s) : null
|
return regex.test(s) ? Number(s) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция для преобразования пустых строк в null
|
const removeNullKeys = <T extends object>(obj: T): Partial<T> =>
|
||||||
function convertEmptyStringsToNull<T extends Record<string, unknown>>(obj: T): T {
|
Object.fromEntries(
|
||||||
const result = { ...obj } as Record<string, unknown>
|
Object.entries(obj).filter(([, v]) => v !== null)
|
||||||
|
) as Partial<T>
|
||||||
Object.keys(result).forEach(key => {
|
|
||||||
if (result[key] === '') {
|
|
||||||
result[key] = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return result as T
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
isDirty,
|
|
||||||
parseIntString,
|
parseIntString,
|
||||||
convertEmptyStringsToNull
|
removeNullKeys
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user