diff --git a/i18n-2.xlsm b/i18n-2.xlsm
index 541682a..36c86b3 100644
Binary files a/i18n-2.xlsm and b/i18n-2.xlsm differ
diff --git a/src/App.vue b/src/App.vue
index 64de8e0..2262f89 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -3,13 +3,14 @@
diff --git a/src/Global-properties.d.ts b/src/Global-properties.d.ts
index 1973d98..377b77b 100644
--- a/src/Global-properties.d.ts
+++ b/src/Global-properties.d.ts
@@ -5,6 +5,8 @@ declare module 'vue' {
$tg: WebApp;
$isDesktop: boolean;
$isMobile: boolean;
+ $isIOS: boolean;
+ $isAndroid: boolean;
}
}
diff --git a/src/boot/global-components.ts b/src/boot/global-components.ts
index bcc5483..cb7c5dc 100644
--- a/src/boot/global-components.ts
+++ b/src/boot/global-components.ts
@@ -5,7 +5,6 @@ import pnPageTemplate from 'components/pnPageTemplate.vue'
import pnAutoAvatar from 'components/pnAutoAvatar.vue'
import pnOverlay from 'components/pnOverlay.vue'
import pnSmallDialog from 'components/pnSmallDialog.vue'
-import pnImageSelector from 'components/pnImageSelector.vue'
import pnShadowScroll from 'components/pnShadowScroll.vue'
import pnMagicOverlay from 'components/pnMagicOverlay.vue'
import pnAccountBlockName from 'components/pnAccountBlockName.vue'
@@ -18,7 +17,6 @@ const components = {
pnPageTemplate,
pnAutoAvatar,
pnOverlay,
- pnImageSelector,
pnSmallDialog,
pnShadowScroll,
pnMagicOverlay,
diff --git a/src/boot/telegram-boot.ts b/src/boot/telegram-boot.ts
index 8207c06..cf6cb91 100644
--- a/src/boot/telegram-boot.ts
+++ b/src/boot/telegram-boot.ts
@@ -25,16 +25,15 @@ export default defineBoot(({ app }) => {
'webk'
].includes(platform)
const isTablet = window.innerWidth > 1024
- if (!isDesktopOrWeb && !isTablet) {
- if (tg.isVersionAtLeast('8.0')) tg.requestFullscreen()
- else tg.expand()
- }
+ if (!isDesktopOrWeb && !isTablet) tg.expand()
tg.SettingsButton.show()
app.config.globalProperties.$tg = tg
app.config.globalProperties.$isDesktop = isDesktopOrWeb || isTablet
app.config.globalProperties.$isMobile = !isDesktopOrWeb && !isTablet
+ app.config.globalProperties.$isIOS = platform === 'ios'
+ app.config.globalProperties.$isAndroid = platform === 'android'
app.provide('tg', tg)
})
@@ -46,7 +45,6 @@ export default defineBoot(({ app }) => {
script setup
import { getCurrentInstance } from 'vue'
-
-const { proxy } = getCurrentInstance()
-and use proxy.$isMobile
+const app = getCurrentInstance()
+const isMobile = app?.appContext.config.globalProperties.$isMobile
*/
\ No newline at end of file
diff --git a/src/components/companyBlock.vue b/src/components/companyBlock.vue
index 25cb1bd..0c4470c 100644
--- a/src/components/companyBlock.vue
+++ b/src/components/companyBlock.vue
@@ -16,7 +16,6 @@
-
-
-
-
-
-
-
- {{$t(input.label) }}
- *
-
-
+
+
+
+
+
+ {{$t(input.label) }}
+ *
+
+
+
@@ -62,8 +63,9 @@
+
+
diff --git a/src/components/meshBackground.vue b/src/components/meshBackground.vue
index e5175b3..439cc76 100644
--- a/src/components/meshBackground.vue
+++ b/src/components/meshBackground.vue
@@ -1,138 +1,106 @@
-
-
+
+
-
diff --git a/src/components/pnActionBar.vue b/src/components/pnActionBar.vue
index 6023f55..fb60754 100644
--- a/src/components/pnActionBar.vue
+++ b/src/components/pnActionBar.vue
@@ -23,7 +23,7 @@
-
diff --git a/src/components/pnBottomSheetDialog.vue b/src/components/pnBottomSheetDialog.vue
index 65f7798..f057c67 100644
--- a/src/components/pnBottomSheetDialog.vue
+++ b/src/components/pnBottomSheetDialog.vue
@@ -4,7 +4,8 @@
transition-show="slide-up"
transition-hide="slide-down"
position="bottom"
- >
+ :persistent="loading"
+ >
-
+
@@ -36,7 +37,7 @@
:height="bodyHeight"
:bottomOffset
:topOffset
- >
+ >
@@ -63,7 +64,11 @@
title: string
caption?: string
maximized?: boolean
- }>(), { maximized: true })
+ loading?: boolean
+ }>(), {
+ maximized: true,
+ loading: false
+ })
const slots = useSlots()
const cardRef = ref()
diff --git a/src/components/pnCutterLongText.vue b/src/components/pnCutterLongText.vue
new file mode 100644
index 0000000..a28e914
--- /dev/null
+++ b/src/components/pnCutterLongText.vue
@@ -0,0 +1,35 @@
+
+
+ {{ text }}
+
+
+
+ {{ textStart }}
+
+
+ {{ textEnd }}
+
+
+
+
+
+
+
diff --git a/src/components/pnImageSelector.vue b/src/components/pnImageSelector.vue
index a39e2dc..55d2025 100644
--- a/src/components/pnImageSelector.vue
+++ b/src/components/pnImageSelector.vue
@@ -1,74 +1,52 @@
-
-
+
+
+
+
-
-
-
-
+ @click="imgFileSelectorClick"
+ />
-
-
-
-
-
-
-
- {{ $t(btn.label) }}
-
-
-
-
+
+
+
+
diff --git a/src/components/pnOnboardBtn.vue b/src/components/pnOnboardBtn.vue
index b56e5df..73bac88 100644
--- a/src/components/pnOnboardBtn.vue
+++ b/src/components/pnOnboardBtn.vue
@@ -22,7 +22,7 @@
-
diff --git a/src/components/pnPageCard.vue b/src/components/pnPageCard.vue
index 70878a7..a4625ee 100644
--- a/src/components/pnPageCard.vue
+++ b/src/components/pnPageCard.vue
@@ -5,7 +5,7 @@
+
+
@@ -58,7 +70,9 @@
diff --git a/src/components/projectBlock.vue b/src/components/projectBlock.vue
index 2a7dae0..495a29e 100644
--- a/src/components/projectBlock.vue
+++ b/src/components/projectBlock.vue
@@ -78,10 +78,11 @@
diff --git a/src/pages/ProjectsPage.vue b/src/pages/ProjectsPage.vue
index cbfb0b4..e56dfda 100644
--- a/src/pages/ProjectsPage.vue
+++ b/src/pages/ProjectsPage.vue
@@ -93,14 +93,13 @@
@@ -270,9 +269,7 @@
})
onMounted(async () => {
- if (!projectsStore.isInit) {
- await projectsStore.init()
- }
+ if (!projectsStore.isInit) await projectsStore.init()
})
watch(showDialogRestoreArchive, (newD :boolean) => {
diff --git a/src/pages/TelegramOnlyPage.vue b/src/pages/TelegramOnlyPage.vue
index 96edba0..99bbd6c 100644
--- a/src/pages/TelegramOnlyPage.vue
+++ b/src/pages/TelegramOnlyPage.vue
@@ -30,7 +30,7 @@
+
+
diff --git a/src/pages/CompanyYourPage.vue b/src/pages/account/CompanyYourPage.vue
similarity index 100%
rename from src/pages/CompanyYourPage.vue
rename to src/pages/account/CompanyYourPage.vue
diff --git a/src/pages/account/SettingsPage.vue b/src/pages/account/SettingsPage.vue
index 6ebe9a0..d42d79b 100644
--- a/src/pages/account/SettingsPage.vue
+++ b/src/pages/account/SettingsPage.vue
@@ -1,9 +1,8 @@
-
+
{{ $t('settings__title') }}
-
{{ $t('settings__software_title') }}
@@ -37,6 +36,29 @@
/>
+
+
+ {{ $t(displayModeLabel) }}
+
+
+
+
+
+
+ {{ $t('settings__display_mode_android_warning') }}
+
+
+
('window')
+ const displayModeOptions = [
+ { value: 'full', label: 'settings__display_mode_full' },
+ { value: 'window', label: 'settings__display_mode_window' }
+ ]
+ const displayModeLabel = computed(() =>
+ displayModeOptions.find(el => el.value === displayMode.value)?.label ?? ''
+ )
+
+ watch(displayMode, async (newValue) => {
+ await settingsStore.updateSettings({ displayMode: newValue })
+ })
+
const timeZoneBot = ref<{ tz: string, offset: number,offsetString: string }>()
watch(timeZoneBot, async (newValue) => {
@@ -123,9 +158,10 @@
onMounted(() => {
locale.value = settingsStore.settings.locale
- localeBot.value = settingsStore.settings.localeBot
fontSize.value = settingsStore.settings.fontSize
+ displayMode.value = settingsStore.settings.displayMode
timeZoneBot.value = settingsStore.settings.timeZoneBot
+ localeBot.value = settingsStore.settings.localeBot
})
diff --git a/src/pages/account/TariffsInfoPage.vue b/src/pages/account/TariffsInfoPage.vue
index 43924ad..8b34257 100644
--- a/src/pages/account/TariffsInfoPage.vue
+++ b/src/pages/account/TariffsInfoPage.vue
@@ -1,7 +1,7 @@
diff --git a/src/pages/project-page/ProjectPageChats.vue b/src/pages/project-page/ProjectPageChats.vue
index 1b858d4..5086af1 100644
--- a/src/pages/project-page/ProjectPageChats.vue
+++ b/src/pages/project-page/ProjectPageChats.vue
@@ -2,12 +2,20 @@
+ >
+
+
@@ -77,7 +85,6 @@
-
-
{
return chats.value
- .filter(searchChats)
+ .filter(BySearch)
- function searchChats (el: Chat) {
+ function BySearch (el: Chat) {
if (!search.value || !(search.value && search.value.trim())) return true
const searchValue = search.value.trim().toLowerCase()
return el.name.toLowerCase().includes(searchValue) ||
diff --git a/src/pages/project-page/ProjectPageCompanies.vue b/src/pages/project-page/ProjectPageCompanies.vue
index af06dbe..62b9f04 100644
--- a/src/pages/project-page/ProjectPageCompanies.vue
+++ b/src/pages/project-page/ProjectPageCompanies.vue
@@ -1,70 +1,88 @@
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('companies__my_company') }}
-
-
-
- {{ item.name }}
-
+
+
+
+
+
-
-
- {{ item.description }}
-
-
-
-
-
-
-
- {{ getQtyUsers(item.id) }}
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ {{ $t('companies__my_company') }}
+
+
+
+ {{ item.name }}
+
+
+
+ {{ item.description }}
+
+
+
+
+
+
+
+ {{ getQtyUsers(item.id) }}
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
(false)
+
+ const search = ref('')
+ const showDialogDeleteCompany = ref(false)
const deleteCompanyId = ref(undefined)
const currentSlideEvent = ref(null)
const closedByUserAction = ref(false)
@@ -157,16 +158,25 @@
}
const users = computed(() => usersStore.users)
- const companies = companiesStore.getCompanies
+ const companies = computed(() => companiesStore.companies)
const companiesInit = computed(() => companiesStore.isInit)
- const myCompany = computed(() => companies.find(el => el.is_own))
+ const myCompany = computed(() => companies.value.find(el => el.is_own))
const displayCompanies = computed(() => {
- const otherComp = companies.filter(el => !el.is_own)
- return myCompany.value
+ const otherComp = companies.value.filter(el => !el.is_own)
+ const c = myCompany.value
? [myCompany.value, ...otherComp]
: otherComp
+
+ return c.filter(searchCompanies)
+
+ function searchCompanies (el: Company) {
+ if (!search.value || !(search.value && search.value.trim())) return true
+ const searchValue = search.value.trim().toLowerCase()
+ return el.name.toLowerCase().includes(searchValue) ||
+ el.description && el.description.toLowerCase().includes(searchValue)
+ }
})
async function maskCompany () {
@@ -194,14 +204,6 @@
closedByUserAction.value = false
}
- function onCancel() {
- closedByUserAction.value = true
- if (currentSlideEvent.value) {
- currentSlideEvent.value.reset()
- currentSlideEvent.value = null
- }
- }
-
async function onConfirm() {
closedByUserAction.value = true
if (deleteCompanyId.value) {
@@ -210,6 +212,14 @@
currentSlideEvent.value = null
}
+ function onCancel() {
+ closedByUserAction.value = true
+ if (currentSlideEvent.value) {
+ currentSlideEvent.value.reset()
+ currentSlideEvent.value = null
+ }
+ }
+
function getQtyUsers (companyId: number) {
const arr = users.value.filter(el => el.company_id === companyId)
return arr.length
@@ -218,11 +228,9 @@
diff --git a/src/router/index.ts b/src/router/index.ts
index 4aa7349..25e709d 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -8,7 +8,6 @@ import {
import routes from './routes'
import { useAuthStore } from 'stores/auth'
import { useProjectsStore } from 'stores/projects'
-import { computed } from 'vue'
const tg = window.Telegram?.WebApp
declare module 'vue-router' {
@@ -35,6 +34,9 @@ export default defineRouter(function (/* { store, ssrContext } */) {
const publicPath = ['404', 'terms', 'privacy', 'consent', 'settings']
+ const authStore = useAuthStore()
+ const projectsStore = useProjectsStore()
+
Router.beforeEach(async (to) => {
if (to.name !== 'telegram_only' && sessionStorage.getItem('isTelegram') === 'false') {
return { name: 'telegram_only', replace: true }
@@ -42,9 +44,6 @@ export default defineRouter(function (/* { store, ssrContext } */) {
if (to.name === 'telegram_only') return true
if (typeof to.name === 'string' && publicPath.includes(to.name)) return true
-
- const authStore = useAuthStore()
- const projectsStore = useProjectsStore()
if (to.name === 'login')
return authStore.isAuth ? { name: 'projects', replace: true } : true
@@ -58,13 +57,9 @@ export default defineRouter(function (/* { store, ssrContext } */) {
if (to.params.id) {
if (!projectsStore.isInit) await projectsStore.init()
-
- const currentProjectId = computed(() => projectsStore.currentProjectId)
-
- if (to.params.id) {
- if (currentProjectId.value !== Number(to.params.id)) {
- if (!projectsStore.projectById(Number(to.params.id)))
- projectsStore.setCurrentProjectId(Number(to.params.id))
+ if (to.params.id && projectsStore.projectById(Number(to.params.id))) {
+ if (projectsStore.currentProjectId !== Number(to.params.id)) {
+ projectsStore.setCurrentProjectId(Number(to.params.id))
}
} else {
projectsStore.setCurrentProjectId(null)
@@ -93,7 +88,9 @@ export default defineRouter(function (/* { store, ssrContext } */) {
}
}
- useProjectsStore().setCurrentProjectId(to.params.id ? Number(to.params.id) : null)
+ if (to.params.id) projectsStore.setLastProjectId(Number(to.params.id))
+ if (to.name === 'projects') projectsStore.setLastProjectId(null)
+ projectsStore.setCurrentProjectId(to.params.id ? Number(to.params.id) : null)
})
return Router
diff --git a/src/router/routes.ts b/src/router/routes.ts
index 87101a1..94e5e28 100644
--- a/src/router/routes.ts
+++ b/src/router/routes.ts
@@ -150,10 +150,15 @@ const routes: RouteRecordRaw[] = [
path: '/support',
component: () => import('pages/account/SupportPage.vue')
},
+ {
+ name: 'backup',
+ path: '/backup',
+ component: () => import('pages/account/BackupPage.vue')
+ },
{
name: 'your_company',
path: '/your-company',
- component: () => import('pages/CompanyYourPage.vue')
+ component: () => import('pages/account/CompanyYourPage.vue')
},
{
name: 'login',
diff --git a/src/stores/SSE.ts b/src/stores/SSE.ts
index f59033c..f3e9154 100644
--- a/src/stores/SSE.ts
+++ b/src/stores/SSE.ts
@@ -67,8 +67,8 @@ export const useSSEStore = defineStore('sse', () => {
eventSource.value.addEventListener('chat-update', (event) => {
const data = JSON.parse(event.data)
- projectsStore.init().catch(() => {}) // for update count of chats on projects page
authStore.updateProfile().catch(() => {}) // for update count of active chats in subscribe
+ projectsStore.init().catch(() => {}) // for update count of chats on projects page
if (data.project_id === currentProjectId.value) {
chatsStore.init().catch(() => {})
usersStore.init().catch(() => {}) // for update users from chats
@@ -78,6 +78,7 @@ export const useSSEStore = defineStore('sse', () => {
eventSource.value.addEventListener('chat-delete', (event) => {
const data = JSON.parse(event.data)
projectsStore.init().catch(() => {}) // for update count of chats on projects page
+
authStore.updateProfile().catch(() => {}) // for update count of active chats in subscribe
if (data.project_id === currentProjectId.value) {
chatsStore.init().catch(() => {})
diff --git a/src/stores/auth.ts b/src/stores/auth.ts
index 9e58ab2..b064636 100644
--- a/src/stores/auth.ts
+++ b/src/stores/auth.ts
@@ -66,6 +66,7 @@ export const useAuthStore = defineStore('auth', () => {
const logout = async () => {
await api.get('/auth/logout', {})
customer.value = null
+ projectsStore.setCurrentProjectId(null)
projectsStore.reset()
}
@@ -93,7 +94,6 @@ export const useAuthStore = defineStore('auth', () => {
const getCodeCurrentEmail = async () =>
await api.post('/auth/email/change-email')
-
const confirmCurrentEmailCode = async (code: string) =>
await api.post('/auth/email/change-email', { code })
@@ -111,7 +111,7 @@ export const useAuthStore = defineStore('auth', () => {
const { data } = await api.get('/customer/profile')
customer.value = data.data
}
-
+
// user data company
const updateMyCompany = async (companyData: CompanyParams) => {
const response = await api.put('/customer/profile', { company: companyData })
diff --git a/src/stores/companies.ts b/src/stores/companies.ts
index a3a08ff..616132e 100644
--- a/src/stores/companies.ts
+++ b/src/stores/companies.ts
@@ -69,8 +69,6 @@ export const useCompaniesStore = defineStore('companies', () => {
companiesMask.value = data.data
}
- const getCompanies = computed(() => companies.value)
-
return {
companies,
companiesMask,
@@ -82,8 +80,7 @@ export const useCompaniesStore = defineStore('companies', () => {
remove,
companyById,
checkCompanyMasked,
- updateMask,
- getCompanies
+ updateMask
}
})
diff --git a/src/stores/files.ts b/src/stores/files.ts
new file mode 100644
index 0000000..44b2ffe
--- /dev/null
+++ b/src/stores/files.ts
@@ -0,0 +1,30 @@
+import { computed } from 'vue'
+import { defineStore } from 'pinia'
+import { api } from 'boot/axios'
+import { useProjectsStore } from 'stores/projects'
+import type { FileLink } from 'types/FileLink'
+
+export const useFilesStore = defineStore('files', () => {
+ const projectsStore = useProjectsStore()
+ const currentProjectId = computed(() => projectsStore.currentProjectId)
+
+ async function getFilesList (projectId: number) {
+ const { data } = await api.get(`/project/${projectId}/file`)
+ return data.data as FileLink[]
+ }
+
+ async function downloadParams (fileId: number) {
+ const { data } = await api.get(`/project/${currentProjectId.value}/file/${fileId}/token`)
+ return data.data
+ ? {
+ url: `/api/download/${data.data.token}`,
+ file_name: data.data.filename as string
+ }
+ : null
+ }
+
+ return {
+ getFilesList,
+ downloadParams
+ }
+})
diff --git a/src/stores/projects.ts b/src/stores/projects.ts
index 86d997f..b4bd975 100644
--- a/src/stores/projects.ts
+++ b/src/stores/projects.ts
@@ -11,24 +11,26 @@ import type { Project, ProjectParams } from 'types/Project'
export const useProjectsStore = defineStore('projects', () => {
const projects = ref([])
const currentProjectId = ref(null)
+ const lastProjectId = ref(null)
const isInit = ref(false)
const chatsStore = useChatsStore()
const usersStore = useUsersStore()
const companiesStore = useCompaniesStore()
const isLoading = ref(false)
- async function init () {
- // guard for double request
+
+ async function init() {
+ // guard for double request
if (isLoading.value) return
isLoading.value = true
+
try {
- reset()
const { data } = await api.get('/project')
const projectsAPI = data.data
- projects.value.push(...projectsAPI)
+ projects.value = projectsAPI
isInit.value = true
- } catch {
- reset()
+ } catch (error) {
+ console.error('Failed to init projects:', error)
} finally {
isLoading.value = false
}
@@ -76,6 +78,10 @@ export const useProjectsStore = defineStore('projects', () => {
currentProjectId.value = (typeof id ==='number') ? id : null
}
+ function setLastProjectId (id: number | null | undefined) {
+ lastProjectId.value = (typeof id ==='number') ? id : null
+ }
+
async function initStores () {
resetStores()
await Promise.all([
@@ -105,12 +111,14 @@ export const useProjectsStore = defineStore('projects', () => {
isInit,
projects,
currentProjectId,
+ lastProjectId,
add,
update,
archive,
restore,
projectById,
setCurrentProjectId,
+ setLastProjectId,
initStores,
resetStores
}
diff --git a/src/stores/settings.ts b/src/stores/settings.ts
index 81ad424..1d1ca9f 100644
--- a/src/stores/settings.ts
+++ b/src/stores/settings.ts
@@ -1,25 +1,32 @@
+import { ref, computed, inject, getCurrentInstance } from 'vue'
import { defineStore } from 'pinia'
-import { ref, computed, inject } from 'vue'
import { api } from 'boot/axios'
import { useAuthStore } from 'stores/auth'
import { useI18n } from 'vue-i18n'
import { Lang } from 'quasar'
import type { WebApp } from '@twa-dev/types'
+const app = getCurrentInstance()
+const isIOS = app?.appContext.config.globalProperties.$isIOS
+const isMobile = app?.appContext.config.globalProperties.$isMobile
+
interface AppSettings {
- fontSize: number
locale: string
- timeZoneBot: { tz: string, offset: number, offsetString: string }
+ fontSize: number
+ displayMode: 'full' | 'window'
localeBot: string
+ timeZoneBot: { tz: string, offset: number, offsetString: string }
}
const defaultFontSize = 16
+const defaultDisplayMode = isIOS && isMobile ? 'full' : 'window'
const defaultSettings: AppSettings = {
- fontSize: defaultFontSize,
locale: 'en-US',
- timeZoneBot: { tz: 'Europe/Moscow', offset: 3, offsetString: '+03:00' },
- localeBot: 'en-US'
+ fontSize: defaultFontSize,
+ displayMode: defaultDisplayMode,
+ localeBot: 'en-US',
+ timeZoneBot: { tz: 'Europe/Moscow', offset: 3, offsetString: '+03:00' }
}
export const useSettingsStore = defineStore('settings', () => {
@@ -98,29 +105,25 @@ export const useSettingsStore = defineStore('settings', () => {
const init = async () => {
if (authStore.isAuth) {
try {
- const response = await api.get<{ data: AppSettings }>('/customer/settings')
- let serverData = response.data.data
- const updateData: Partial = {}
-
- if (!serverData.fontSize) updateData.fontSize = defaultSettings.fontSize
- if (!serverData.locale) updateData.locale = detectLocale()
- if (!serverData.timeZoneBot) updateData.timeZoneBot = defaultSettings.timeZoneBot
- if (!serverData.localeBot) updateData.localeBot = detectLocale()
-
- if (Object.keys(updateData).length > 0) {
- await api.put('/customer/settings', updateData)
- const response = await api.get<{ data: AppSettings }>('/customer/settings')
- serverData = response.data.data
+ const { data } = await api.get('/customer/settings')
+ settings.value = {
+ locale: data.data.locale || detectLocale(),
+ fontSize: data.data.fontSize || defaultSettings.fontSize,
+ displayMode: data.data.displayMode || defaultSettings.displayMode,
+ localeBot: data.data.localeBot || detectLocale(),
+ timeZoneBot: data.data.timeZoneBot || defaultSettings.timeZoneBot
}
-
- settings.value = serverData
+ if (!data.data.locale || !data.data.fontSize || !data.data.displayMode ||
+ !data.data.localeBot || !data.data.timeZoneBot
+ ) await saveSettings()
} catch {
settings.value.locale = detectLocale()
}
} else {
settings.value = {
...defaultSettings,
- locale: detectLocale()
+ locale: detectLocale(),
+ localeBot: detectLocale()
}
}
updateCssVariable()
@@ -128,6 +131,11 @@ export const useSettingsStore = defineStore('settings', () => {
isInit.value = true
}
+ const saveSettings = async () => {
+ const { data } = await api.put('/settings', settings.value)
+ if (data.data) settings.value = data.data
+ }
+
const updateSettings = async (newSettings: Partial) => {
settings.value = { ...settings.value, ...newSettings }
updateCssVariable()
diff --git a/src/types/FileLink.ts b/src/types/FileLink.ts
new file mode 100644
index 0000000..3429e3b
--- /dev/null
+++ b/src/types/FileLink.ts
@@ -0,0 +1,23 @@
+interface FileLink {
+ id: number
+ filename: string
+ mime: string
+ size: number
+ published_by: number
+ published: number
+ parent: {
+ id: number
+ name: string
+ } & (
+ | { type: 'task' | 'meeting' }
+ | {
+ type: 'chat'
+ is_deleted: boolean
+ }
+ )
+ [key: string]: unknown
+}
+
+export type {
+ FileLink
+}
diff --git a/src/utils/files-functions.ts b/src/utils/files-functions.ts
new file mode 100644
index 0000000..ac93512
--- /dev/null
+++ b/src/utils/files-functions.ts
@@ -0,0 +1,124 @@
+type TranslateFunction = (key: string) => string
+
+const fileSize = (value: number, t: TranslateFunction): string => {
+ if (value === 0) return '-'
+
+ const units = ['B', 'kB', 'MB', 'GB', 'TB'] as const
+ const i = Math.floor(Math.log(value) / Math.log(1024))
+
+ const index = Math.max(0, Math.min(i, units.length - 1))
+
+ const result = (value / Math.pow(1024, index)).toFixed(1)
+
+ const unitKey = units[index]
+
+ return `${result} ${t(unitKey!)}`
+}
+
+
+ interface ParsedFile {
+ name: string
+ ext: string
+ }
+
+ function parseFileName(filename: string): ParsedFile {
+ const lastDotIndex = filename.lastIndexOf('.')
+
+ if (lastDotIndex === -1) {
+ return { name: filename, ext: '' }
+ }
+
+ return {
+ name: filename.slice(0, lastDotIndex),
+ ext: filename.slice(lastDotIndex + 1).toLowerCase()
+ }
+ }
+
+ interface FileIcon {
+ type: string
+ icon: string
+ color: string
+ }
+
+ function fileIcon(filename: string): FileIcon {
+ const ext = parseFileName(filename).ext
+
+ switch (ext) {
+ case 'doc':
+ case 'docx':
+ return { type: 'doc', icon: 'pn-icon-file-doc', color: '#2B579A' }
+
+ case 'xls':
+ case 'xlsx':
+ case 'csv':
+ return { type: 'xls', icon: 'pn-icon-file-xls', color: '#217346' }
+
+ case 'vsd':
+ case 'vsdx':
+ return { type: 'vsd', icon: 'pn-icon-file-vsd', color: '#3955A3' }
+
+ case 'ppt':
+ case 'pptx':
+ return { type: 'ppt', icon: 'pn-icon-file-ppt', color: '#D24726' }
+
+ case 'pdf':
+ return { type: 'pdf', icon: 'pn-icon-file-pdf', color: '#D0021B' }
+
+ case 'png':
+ case 'jpg':
+ case 'jpeg':
+ case 'gif':
+ case 'bmp':
+ case 'webp':
+ case 'svg':
+ return { type: 'img', icon: 'pn-icon-file-img', color: '#4CAF50' }
+
+ case 'mp3':
+ case 'wav':
+ case 'ogg':
+ return { type: 'music', icon: 'pn-icon-file-audio', color: '#FF9800' }
+
+ case 'mp4':
+ case 'avi':
+ case 'mov':
+ case 'mkv':
+ return { type: 'video', icon: 'pn-icon-file-video', color: '#9C27B0' }
+
+ case 'js':
+ case 'ts':
+ case 'html':
+ case 'css':
+ case 'json':
+ case 'xml':
+ return { type: 'code', icon: 'pn-icon-file-code', color: '#999' }
+
+ case 'txt':
+ return { type: 'txt', icon: 'pn-icon-file-txt', color: '#757575' }
+
+ case 'zip':
+ case 'rar':
+ case '7z':
+ case 'tar':
+ case 'gz':
+ return { type: 'archive', icon: 'pn-icon-file-archive', color: '#F78E1E' }
+
+ case 'skp':
+ return { type: 'skp', icon: 'pn-icon-file-skp', color: '#CC0000' }
+
+ case 'dwg':
+ case 'dxf':
+ return { type: 'cad', icon: 'pn-icon-file-dwg', color: '#00579D' }
+
+ case 'ttf':
+ return { type: 'font', icon: 'pn-icon-file-ttf', color: '#607D8B' }
+
+ default:
+ return { type: 'common', icon: 'pn-icon-file-default', color: '#9E9E9E' }
+ }
+ }
+
+export {
+ fileSize,
+ parseFileName,
+ fileIcon
+}
\ No newline at end of file