update empty string to null

This commit is contained in:
2025-07-28 18:17:52 +03:00
parent 6d71c60550
commit 462ed2b671
33 changed files with 369 additions and 242 deletions

Binary file not shown.

View File

@@ -17,11 +17,9 @@
<!--
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
-->
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="icon" type="image/ico" href="favicon.ico">
<link rel="icon" href="icons/favicon.svg" type="image/svg+xml">
<link rel="icon" type="image/ico" href="icons/favicon.ico">
<link rel="apple-touch-icon" href="icons/apple-touch-icon.png">
</head>
<body>
<!-- quasar:entry-point -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
public/icons/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

67
public/icons/favicon.svg Normal file
View File

@@ -0,0 +1,67 @@
<svg
viewBox="0 0 8.4666662 8.4666662"
width="32"
height="32"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
>
<defs id="defs1" />
<g id="layer1">
<rect
style="fill: #27A7E7 ;stroke-width:0.233149"
id="rect5"
width="6.9885192"
height="0.35581663"
x="3.114475"
y="0.86827624"
transform="matrix(0.77578367,0.63099897,-0.77578367,0.63099897,0,0)"
/>
<rect
style="fill: #27A7E7 ;stroke-width:0.24961"
id="rect5-7"
width="7.4819207"
height="0.3809379"
x="-3.9267058"
y="5.7988153"
transform="matrix(-0.70756824,0.70664502,0.70756824,0.70664502,0,0)"
/>
<circle
style="fill: #27A7E7 ;stroke-width:0.134869"
id="path5-8"
cx="1.5875"
cy="6.8791666"
r="1.0583333"
/>
<circle
style="fill: #27A7E7 ;stroke-width:0.168586"
id="path5-8-5"
cx="7.1437502"
cy="7.1437502"
r="1.3229166"
/>
<circle
style="fill: #27A7E7 ;stroke-width:0.118011"
id="path5-8-5-1"
cx="1.4552083"
cy="2.5135417"
r="0.92604166"
/>
<circle
style="fill: #27A7E7 ;stroke-width:0.101152"
id="path5-8-5-1-7"
cx="7.1437502"
cy="1.3229166"
r="0.79374999"
/>
<circle
style="fill: #F36D3A; stroke-width:0.23602"
id="path5"
cx="3.96875"
cy="4.4979167"
r="1.8520833"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@@ -1,7 +1,13 @@
import { defineBoot } from '#q-app/wrappers'
import axios, { type AxiosError } from 'axios'
import axios, { type AxiosError, type AxiosRequestConfig } from 'axios'
import { Notify } from 'quasar'
declare module 'axios' {
export interface AxiosRequestConfig {
suppressNotify?: boolean
}
}
class ServerError extends Error {
constructor(
public code: string,
@@ -31,12 +37,15 @@ api.interceptors.response.use(
errorData.message
)
Notify.create({
type: 'negative',
message: errorData.code + ': ' + errorData.message,
icon: 'mdi-alert-outline',
position: 'bottom'
})
if (!error.config?.suppressNotify) {
Notify.create({
type: 'negative',
message: errorData.code + ': ' + errorData.message,
icon: 'mdi-alert-outline',
position: 'bottom'
})
}
return Promise.reject(serverError)
}
)

View File

@@ -1,7 +1,7 @@
<template>
<div class="flex row items-center">
<div class="flex row items-center no-wrap">
<svg
class="iconcolor q-mr-sm"
class="iconcolor q-mr-xs"
viewBox="0 0 8.4666662 8.4666662"
width="32"
height="32"
@@ -68,12 +68,17 @@
</g>
</svg>
<span class="text-h4 q-pa-0" style="color: var(--logo-color-bg-white);">
projects
<span
class="text-h4 text-brand"
style="
color: var(--logo-color-bg-white);
margin-right: 2px;"
>
tg
</span>
<span class="text-h4 text-brand text-bold q-pa-0">
Node
<span class="text-h4 text-brand2 text-bold q-pa-0">
Crew
</span>
</div>
@@ -99,16 +104,16 @@
}
.iconcolor {
--icon-color: var(--logo-color-bg-white);
--icon-color: #27A7E7;
}
@keyframes blink {
100%,
0% {
fill: $light-green-14;
fill: #fa9e7a;
}
60% {
fill: $green-14;
fill: #F36D3A;
}
}

View File

@@ -126,7 +126,6 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { AxiosError } from 'axios'
import { useQuasar } from 'quasar'
import { useI18n } from "vue-i18n"
import { QInput } from 'quasar'
import { useAuthStore, type AuthFlowType } from 'stores/auth'
@@ -141,7 +140,6 @@
: 'changeMethod'
})
const $q = useQuasar()
const { t } = useI18n()
const authStore = useAuthStore()
@@ -178,34 +176,17 @@
})
const stepActions: Record<Step, () => Promise<void>> = {
1: async () => {
await authStore.initRegistration(flowType.value, login.value)
},
2: async () => {
await authStore.confirmCode(flowType.value, login.value, code.value)
},
3: async () => {
await authStore.setPassword(flowType.value, login.value, code.value, password.value)
if (flowType.value === 'register' || flowType.value === 'changeMethod') {
await authStore.loginWithCredentials(login.value, password.value)
}
}
}
const handleError = (err: AxiosError) => {
const error = err as AxiosError<{ error?: { message?: string } }>
const message = error.response?.data?.error?.message || t('unknown_error')
$q.notify({
message: `${t('error')}: ${message}`,
type: 'negative',
position: 'bottom',
timeout: 2500
})
if (step.value > 1) {
code.value = ''
password.value = ''
1: async () => {
await authStore.initRegistration(flowType.value, login.value)
},
2: async () => {
await authStore.confirmCode(flowType.value, login.value, code.value)
},
3: async () => {
await authStore.setPassword(flowType.value, login.value, code.value, password.value)
if (flowType.value === 'register' || flowType.value === 'changeMethod') {
await authStore.loginWithCredentials(login.value, password.value)
}
}
}
@@ -218,7 +199,7 @@
showSuccessOverlay.value = true
}
} catch (error) {
handleError(error as AxiosError)
console.error(error as AxiosError)
}
}

View File

@@ -7,8 +7,8 @@
<q-btn
rounded color="primary"
class="w100 q-mt-md q-mb-xs"
:disable="!(isFormValid && (isDirty(initialCompany, modelValue)))"
@click = "emit('update')"
:disable="!isFormValid"
@click = "onSubmit"
>
{{ $t(btnText) }}
</q-btn>
@@ -57,10 +57,10 @@
</template>
<script setup lang="ts">
import {onMounted, computed, ref } from 'vue'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { isDirty } from 'helpers/helpers'
import type { CompanyParams } from 'types/Company'
import { convertEmptyStringsToNull } from 'helpers/helpers'
const { t }= useI18n()
@@ -108,13 +108,11 @@
return Object.values(validations).every(Boolean)
})
const initialCompany = ref({} as CompanyParams)
function onSubmit() {
const cleanedData = convertEmptyStringsToNull(modelValue.value)
emit('update', cleanedData)
}
onMounted(() => {
console.log(111, modelValue.value)
initialCompany.value = { ...modelValue.value }
})
</script>
<style scoped>

View File

@@ -7,7 +7,7 @@
<q-btn
rounded color="primary"
class="w100 q-mt-md q-mb-xs"
:disable="!(isFormValid && (isDirty(initialProject, modelValue)))"
:disable="!isFormValid"
@click = "emit('update')"
>
{{ $t(btnText) }}
@@ -64,7 +64,6 @@
<script setup lang="ts">
import { onMounted, computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { isDirty } from 'helpers/helpers'
import type { ProjectParams } from 'types/Project'
const { t } = useI18n()

View File

@@ -7,8 +7,7 @@
<q-btn
rounded color="primary"
class="w100 q-mt-md q-mb-xs"
:disable="!(isDirty(initialUser, modelValue))"
@click = "emit('update')"
@click = "onSubmit"
>
{{ $t(btnText) }}
</q-btn>
@@ -108,12 +107,13 @@
</template>
<script setup lang="ts">
import { onMounted, computed, ref } from 'vue'
import { computed } from 'vue'
import { useCompaniesStore } from 'stores/companies'
import { useI18n } from 'vue-i18n'
import { isDirty } from 'helpers/helpers'
import { convertEmptyStringsToNull } from 'src/helpers/helpers'
import type { User } from 'types/Users'
const { t } = useI18n()
const modelValue = defineModel<User>({
@@ -127,11 +127,10 @@
const emit = defineEmits(['update'])
const initialUser = ref({} as User)
onMounted(() => {
initialUser.value = { ...modelValue.value }
})
function onSubmit() {
const cleanedData = convertEmptyStringsToNull(modelValue.value)
emit('update', cleanedData)
}
const companiesStore = useCompaniesStore()
const companies = computed(() => companiesStore.companies)

View File

@@ -1,10 +1,18 @@
// app global css in SCSS form
.text-brand {
color: $green-14 !important;
color: #27A7E7 !important;
}
.bg-brand {
background: $green-14 !important;
background: #27A7E7 !important;
}
.text-brand2 {
color: #F36D3A !important;
}
.bg-brand2 {
background: #F36D3A !important;
}
$base-width: 100;
@@ -65,3 +73,12 @@ body {
border-bottom: 1px solid grey;
margin: auto;
}
@font-face {
font-family: 'myFont';
src: url(./fonts/OpenSans-Regular.woff2);
}
button[disabled] {
background-color: $grey-5 !important;
}

Binary file not shown.

View File

@@ -12,7 +12,7 @@
// to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary : #1976D2;
$primary : #27A7E7;
$secondary : #26A69A;
$accent : #9C27B0;
@@ -26,4 +26,16 @@ $warning : #F2C037;
$lightgrey : #DCDCDC;
$body-font-size: var(--dynamic-font-size)
$body-font-size: var(--dynamic-font-size);
$typography-font-family: 'myFont', Roboto !default;
body, html, #q-app {
font-family: $typography-font-family;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
font-family: inherit;
}

View File

@@ -92,7 +92,22 @@ function parseIntString (s: string | string[] | undefined) :number | null {
return regex.test(s) ? Number(s) : null
}
// Функция для преобразования пустых строк в null
function convertEmptyStringsToNull<T extends Record<string, unknown>>(obj: T): T {
const result = { ...obj } as Record<string, unknown>
Object.keys(result).forEach(key => {
if (result[key] === '') {
result[key] = null
}
})
return result as T
}
export {
isDirty,
parseIntString
parseIntString,
convertEmptyStringsToNull
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -192,12 +192,10 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { AxiosError } from 'axios'
import { useQuasar } from 'quasar'
import { useI18n } from "vue-i18n"
import { QInput } from 'quasar'
import { useAuthStore } from 'stores/auth'
const $q = useQuasar()
const { t } = useI18n()
const authStore = useAuthStore()
@@ -231,39 +229,22 @@
})
const stepActions: Record<Step, () => Promise<void>> = {
1: async () => {
await authStore.getCodeCurrentEmail()
},
2: async () => {
await authStore.confirmCurrentEmailCode(code.value)
console.log(code.value)
},
3: async () => {
await authStore.getCodeNewEmail(code.value, newLogin.value)
},
4: async () => {
await authStore.confirmNewEmailCode(code.value, newCode.value, newLogin.value,)
},
5: async () => {
await authStore.setNewEmailPassword(code.value, newCode.value, newLogin.value, password.value)
await authStore.loginWithCredentials(newLogin.value, password.value)
}
}
const handleError = (err: AxiosError) => {
const error = err as AxiosError<{ error?: { message?: string } }>
const message = error.response?.data?.error?.message || t('unknown_error')
$q.notify({
message: `${t('error')}: ${message}`,
type: 'negative',
position: 'bottom',
timeout: 2500
})
if (step.value > 1) {
code.value = ''
password.value = ''
1: async () => {
await authStore.getCodeCurrentEmail()
},
2: async () => {
await authStore.confirmCurrentEmailCode(code.value)
console.log(code.value)
},
3: async () => {
await authStore.getCodeNewEmail(code.value, newLogin.value)
},
4: async () => {
await authStore.confirmNewEmailCode(code.value, newCode.value, newLogin.value,)
},
5: async () => {
await authStore.setNewEmailPassword(code.value, newCode.value, newLogin.value, password.value)
await authStore.loginWithCredentials(newLogin.value, password.value)
}
}
@@ -276,7 +257,7 @@
showSuccessOverlay.value = true
}
} catch (error) {
handleError(error as AxiosError)
console.error(error as AxiosError)
}
}

View File

@@ -1,7 +1,7 @@
<template>
<pn-page-card>
<template #title>
{{$t('login__register')}}
{{$t('login__register_title')}}
</template>
<pn-scroll-list>
<account-helper :type :email/>

91
src/pages/ChatPage.vue Normal file
View File

@@ -0,0 +1,91 @@
<template>
<pn-page-card>
<template #title>
<pn-account-block-name/>
<q-btn
@click="logout()"
flat
round
icon="mdi-logout"
/>
</template>
<pn-scroll-list>
<q-list separator>
<q-item
v-for="item in displayItems"
:key="item.id"
@click="goTo(item.pathName)"
clickable
v-ripple
>
<q-item-section avatar>
<q-avatar
:icon="item.icon"
:color="item.iconColor ? item.iconColor: 'brand'"
text-color="white"
rounded
font-size ="26px"
/>
</q-item-section>
<q-item-section>
<q-item-label>
{{ $t(item.name) }}
</q-item-label>
<q-item-label class="text-caption" v-if="$te(item.description)">
{{ $t(item.description) }}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</pn-scroll-list>
</pn-page-card>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from 'stores/auth'
const router = useRouter()
const authStore = useAuthStore()
interface ItemList {
id: number
name: string
description?: string
icon: string
iconColor?: string
pathName: string
display?: boolean
}
const items = computed(() => ([
{ id: 1, name: 'account__subscribe', description: 'account__subscribe_description', icon: 'mdi-crown-circle-outline', iconColor: 'orange', pathName: 'subscribe' },
{ id: 2, name: 'account__auth_change_method', description: 'account__auth_change_method_description', icon: 'mdi-account-sync-outline', iconColor: 'primary', pathName: 'change_account_auth_method', display: !authStore.customer?.email },
{ id: 3, name: 'account__auth_change_password', description: 'account__auth_change_password_description', icon: 'mdi-account-key-outline', iconColor: 'primary', pathName: 'change_account_password', display: !!authStore.customer?.email },
{ id: 4, name: 'account__auth_change_account', description: 'account__auth_change_account_description', icon: 'mdi-account-switch-outline', iconColor: 'primary', pathName: 'change_account_email', display: !!authStore.customer?.email },
{ id: 5, name: 'account__company_data', icon: 'mdi-account-group-outline', description: 'account__company_data_description', pathName: 'your_company' },
{ id: 6, name: 'account__settings', icon: 'mdi-cog-outline', description: 'account__settings_description', iconColor: 'info', pathName: 'settings' },
{ id: 7, name: 'account__support', icon: 'mdi-lifebuoy', description: 'account__support_description', iconColor: 'info', pathName: 'support' },
{ id: 9, name: 'account__terms_of_use', icon: 'mdi-book-open-variant-outline', description: '', iconColor: 'grey', pathName: 'terms' },
{ id: 10, name: 'account__privacy', icon: 'mdi-lock-outline', description: '', iconColor: 'grey', pathName: 'privacy' }
]))
const displayItems = computed(() => (
items.value.filter((item: ItemList) => !('display' in item) || item.display === true)
))
async function goTo (path: string) {
await router.push({ name: path })
}
async function logout () {
await authStore.logout()
await router.push({ name: 'login' })
}
</script>
<style lang="scss">
</style>

View File

@@ -8,7 +8,7 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { reactive } from 'vue'
import { useRouter } from 'vue-router'
import companyBlock from 'components/companyBlock.vue'
import { useCompaniesStore } from 'stores/companies'
@@ -17,18 +17,10 @@
const router = useRouter()
const companiesStore = useCompaniesStore()
const newCompany = ref(<CompanyParams>{
name: '',
logo: '',
description: '',
site: '',
address: '',
phone: '',
email: ''
})
const newCompany = reactive<CompanyParams>({} as CompanyParams)
async function addCompany () {
await companiesStore.add(newCompany.value)
async function addCompany(companyData: CompanyParams) {
await companiesStore.add(companyData)
router.go(-1)
}

View File

@@ -1,12 +1,12 @@
<template>
<company-block
v-if="companyMod"
v-model="companyMod"
v-if="company"
v-model="company"
title="company_edit__title_card"
btnText="company_edit__btn"
@update="updateCompany"
>
<template #myCompany v-if="companyMod.is_own">
<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"/>
@@ -32,24 +32,27 @@
const route = useRoute()
const companiesStore = useCompaniesStore()
const companyMod = ref<CompanyParams | null>(null)
const company = ref<CompanyParams | null>(null)
const companyId = computed(() => parseIntString(route.params.companyId))
if (companiesStore.isInit) {
companyMod.value = companyId.value
? { ...companiesStore.companyById(companyId.value) } as CompanyParams
: null
function initCompany() {
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 && companyId.value && !companyMod.value) {
companyMod.value = { ...companiesStore.companyById(companyId.value) as CompanyParams }
}
if (isInit) initCompany()
})
async function updateCompany () {
if (companyId.value && companyMod.value) {
await companiesStore.update(companyId.value, companyMod.value)
async function updateCompany(companyData: CompanyParams) {
if (companyId.value) {
await companiesStore.update(companyId.value, companyData)
router.go(-1)
}
}

View File

@@ -1,18 +1,23 @@
<template>
<q-page class="flex column items-center justify-between">
<div :style="{ height: `${blockHeight}px` }" />
<q-page
class="q-ma-none w100"
>
<q-scroll-area
style="height: 100vh"
>
<div class="flex items-center justify-center" style="height: 100vh">
<q-card
id="login_block"
flat
class="flex column items-center w80 justify-between q-py-md login-card "
class="flex column no-wrap items-center justify-between q-px-xl q-py-md login-card"
style="max-width: 400px;"
>
<login-logo
class="col-grow q-pa-md"
<base-logo
class="col-grow q-pa-lg"
:style="{ alignItems: 'flex-end' }"
/>
<div class="q-ma-md flex column input-login">
<div class="flex column w100">
<q-input
v-model="login"
autofocus
@@ -67,29 +72,30 @@
</q-btn>
</div>
</div>
<q-btn
@click="sendAuth()"
color="primary"
:disabled="!acceptTermsOfUse || !isEmailValid || !isPasswordValid"
>
{{$t('login__sign_in')}}
</q-btn>
<div class="q-pt-lg">
<q-btn
@click="sendAuth()"
color="primary"
class="w100 q-my-md"
:disabled="!isEmailValid || !isPasswordValid"
>
{{$t('login__sign_in')}}
</q-btn>
<q-btn
flat
sm
no-caps
class="q-my-md"
color="primary"
@click="createAccount"
>
{{$t('login__register')}}
</q-btn>
</div>
<div
v-if="isTelegramApp"
id="alt_login"
class="w80 q-flex column items-center q-pt-md"
class="flex w100 column items-center "
>
<div
class="orline w100 text-grey"
@@ -101,7 +107,7 @@
sm
no-caps
color="primary"
:disabled="!acceptTermsOfUse"
class="q-my-md"
@click="handleTelegramLogin"
>
<div class="flex items-center text-blue">
@@ -122,45 +128,19 @@
</q-btn>
</div>
</q-card>
<div
id="term-of-use"
class="q-pb-md text-white flex justify-center row text-caption"
ref="bottomBlock"
>
<q-resize-observer @resize="syncHeights" />
<q-checkbox
v-model="acceptTermsOfUse"
checked-icon="task_alt"
unchecked-icon="highlight_off"
:color="acceptTermsOfUse ? 'brand' : 'red'"
dense
keep-color
size="sm"
/>
<span class="q-px-xs">
{{ $t('login__accept_terms_of_use') + ' ' }}
</span>
<span
@click="router.push('terms-of-use')"
style="text-decoration: underline;"
>
{{ $t('login__terms_of_use') }}
</span>
</div>
</q-scroll-area>
</q-page>
</template>
<script setup lang="ts">
import { ref, computed, inject, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import loginLogo from 'components/login-page/loginLogo.vue'
import baseLogo from 'components/BaseLogo.vue'
import { useI18n } from "vue-i18n"
import { useAuthStore } from 'stores/auth'
import type { WebApp } from '@twa-dev/types'
import { QInput } from 'quasar'
import { useNotify, type ServerError } from 'composables/useNotify'
const { notifyError } = useNotify()
type ValidationRule = (val: string) => boolean | string
@@ -174,10 +154,6 @@
const login = ref<string>('')
const password = ref<string>('')
const isPwd = ref<boolean>(true)
const acceptTermsOfUse = ref<boolean>(true)
const bottomBlock = ref<HTMLDivElement | null>(null)
const blockHeight = ref<number>(0)
const emailInput = ref<InstanceType<typeof QInput>>()
const passwordInput = ref<InstanceType<typeof QInput>>()
@@ -223,7 +199,7 @@
async function sendAuth() {
try { void await authStore.loginWithCredentials(login.value, password.value) }
catch (error) {
notifyError(error as ServerError)
console.error(error)
}
await router.push({ name: 'projects' })
}
@@ -251,12 +227,6 @@
await router.push({ name: 'projects' })
}
function syncHeights() {
if (bottomBlock.value) {
blockHeight.value = bottomBlock.value.offsetHeight
}
}
onUnmounted(() => {
Object.values(validateTimerId.value).forEach(timer => timer && clearTimeout(timer))
})
@@ -264,16 +234,6 @@
</script>
<style scoped>
.maxh15 {
max-height: calc(100Vh *0.15);
}
.input-login {
width: calc(100% * 0.8);
max-width: 300px;
}
.login-card {
opacity: 0.9 !important;
border-radius: var(--top-raduis);

View File

@@ -10,13 +10,12 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useRouter } from 'vue-router'
import projectBlock from 'components/projectBlock.vue'
import { useProjectsStore } from 'stores/projects'
import type { ProjectParams } from 'types/Project'
const router = useRouter()
const route = useRoute()
const projectsStore = useProjectsStore()
const projectId = computed(() => projectsStore.currentProjectId)

View File

@@ -1,7 +1,7 @@
<template>
<user-block
v-if="userMod"
v-model="userMod"
v-if="user"
v-model="user"
title="user_edit__title_card"
btnText="user_edit__btn"
@update="updateUser"
@@ -20,24 +20,25 @@
const route = useRoute()
const usersStore = useUsersStore()
const userMod = ref<User | null>(null)
const user = ref<User | null>(null)
const userId = computed(() => parseIntString(route.params.userId))
if (usersStore.isInit) {
userMod.value = userId.value
? { ...usersStore.userById(userId.value) } as User
: null
function initUser() {
if (usersStore.isInit && userId.value) {
const foundUser = usersStore.userById(userId.value)
if (foundUser) {
user.value = { ...foundUser } as User
}
}
}
watch(() => usersStore.isInit, (isInit) => {
if (isInit && userId.value && !userMod.value) {
userMod.value = { ...usersStore.userById(userId.value) as User }
}
})
if (usersStore.isInit) initUser()
async function updateUser () {
if (userId.value && userMod.value) {
await usersStore.update(userId.value, userMod.value)
watch(() => usersStore.isInit, initUser)
async function updateUser (userData: User) {
if (userId.value) {
await usersStore.update(userId.value, userData)
router.go(-1)
}
}

View File

@@ -40,7 +40,7 @@ export const useAuthStore = defineStore('auth', () => {
const initialize = async () => {
try {
const { data } = await api.get('/customer/profile')
const { data } = await api.get('/customer/profile', { suppressNotify: true })
customer.value = data.data
const socket = new WebSocket("wss://946gp81j-9000.euw.devtunnels.ms/api/admin")
console.log(socket)

View File

@@ -1,18 +1,17 @@
interface CompanyParams {
name: string
description: string
address: string
site: string
phone: string
email: string
logo: string
[key: string]: string | number
description: string | null
address: string | null
site: string | null
phone: string | null
email: string | null
logo: string | null
[key: string]: string | number | null
}
interface Company extends CompanyParams {
id: number
project_id: number
[key: string]: string | number
}
interface CompanyMask {

View File

@@ -1,11 +1,12 @@
interface UserParams {
fullname: string
department: string
role: string
phone: string
email: string
fullname: string | null
department: string | null
role: string | null
phone: string | null
email: string | null
is_blocked: boolean
company_id: number | null
[key: string]: string | boolean | number | null
}
interface User extends UserParams {
@@ -17,7 +18,6 @@ interface User extends UserParams {
username: string | null
photo: string | null
is_leave: boolean
[key: string]: unknown
}
export type {