From 1c732e16dd040026203870159339c3fc7c535de9 Mon Sep 17 00:00:00 2001 From: CCTVcalc Date: Thu, 5 Jun 2025 20:00:58 +0300 Subject: [PATCH] v1 --- backend/apps/admin.js | 720 ++++++++++++------ backend/apps/bot.js | 625 ++++++++------- backend/data/init.sql | 55 +- i18n-2.xlsm | Bin 49850 -> 40816 bytes icons-svg/_old/icon-3-points.svg | 36 + icons-svg/_old/icon-bell-solid.svg | 31 + icons-svg/_old/icon-bell.svg | 31 + icons-svg/_old/icon-bookmark.svg | 64 ++ icons-svg/_old/icon-calendar.svg | 64 ++ icons-svg/_old/icon-change.svg | 36 + icons-svg/_old/icon-clear.svg | 64 ++ icons-svg/_old/icon-clipboard.svg | 36 + icons-svg/_old/icon-download.svg | 36 + icons-svg/_old/icon-dropdown.svg | 32 + icons-svg/_old/icon-exit.svg | 36 + icons-svg/_old/icon-eye.svg | 66 ++ icons-svg/_old/icon-file-add.svg | 69 ++ icons-svg/_old/icon-file-archive.svg | 36 + icons-svg/_old/icon-file-audio.svg | 36 + icons-svg/_old/icon-file-delete.svg | 69 ++ icons-svg/_old/icon-file-doc.svg | 71 ++ icons-svg/_old/icon-file-dwg.svg | 36 + icons-svg/_old/icon-file-img.svg | 36 + icons-svg/_old/icon-file-lock.svg | 36 + icons-svg/_old/icon-file-merge.svg | 36 + icons-svg/_old/icon-file-move.svg | 36 + ...on-file-move.svg.2019_10_02_17_03_29.0.svg | 64 ++ icons-svg/_old/icon-file-pdf.svg | 69 ++ icons-svg/_old/icon-file-ppt.svg | 36 + icons-svg/_old/icon-file-rename.svg | 69 ++ icons-svg/_old/icon-file-skp.svg | 36 + icons-svg/_old/icon-file-ttf.svg | 69 ++ icons-svg/_old/icon-file-txt.svg | 69 ++ icons-svg/_old/icon-file-types.svg | 202 +++++ icons-svg/_old/icon-file-update.svg | 69 ++ icons-svg/_old/icon-file-video.svg | 36 + icons-svg/_old/icon-file-vsd.svg | 36 + icons-svg/_old/icon-file-xls.svg | 36 + icons-svg/_old/icon-file.svg | 35 + icons-svg/_old/icon-filter.svg | 36 + icons-svg/_old/icon-flag-solid.svg | 32 + icons-svg/_old/icon-flag.svg | 31 + icons-svg/_old/icon-folder-delete.svg | 32 + icons-svg/_old/icon-folder-move.svg | 32 + icons-svg/_old/icon-folder-over.svg | 73 ++ icons-svg/_old/icon-folder-plus.svg | 32 + icons-svg/_old/icon-folder-rename.svg | 36 + icons-svg/_old/icon-folder-update.svg | 36 + icons-svg/_old/icon-folder.svg | 32 + icons-svg/_old/icon-full-view.svg | 32 + icons-svg/_old/icon-gear.svg | 35 + icons-svg/_old/icon-info.svg | 31 + icons-svg/_old/icon-left-arrow.svg | 31 + icons-svg/_old/icon-lock-close.svg | 36 + icons-svg/_old/icon-lock-open.svg | 35 + icons-svg/_old/icon-logo-2.svg.png | Bin 0 -> 4584 bytes icons-svg/_old/icon-logo.png | Bin 0 -> 6087 bytes icons-svg/_old/icon-logo.svg | 36 + icons-svg/_old/icon-mail.svg | 31 + icons-svg/_old/icon-messege-2.svg | 69 ++ icons-svg/_old/icon-messege-2.svg.png | Bin 0 -> 405 bytes icons-svg/_old/icon-messege-3.svg.png | Bin 0 -> 90674 bytes icons-svg/_old/icon-messege-done.svg | 36 + icons-svg/_old/icon-messege-second.svg | 35 + icons-svg/_old/icon-messege.svg | 35 + icons-svg/_old/icon-open-folder.svg | 32 + icons-svg/_old/icon-paperclip.svg | 64 ++ icons-svg/_old/icon-pencil.svg | 35 + icons-svg/_old/icon-plus.svg | 32 + icons-svg/_old/icon-question.svg | 40 + icons-svg/_old/icon-question2.svg | 70 ++ icons-svg/_old/icon-right-arrow.svg | 31 + icons-svg/_old/icon-save (2).svg | Bin 0 -> 3440 bytes icons-svg/_old/icon-save.svg | 36 + icons-svg/_old/icon-search-1.svg | 146 ++++ icons-svg/_old/icon-search-file.svg | 36 + icons-svg/_old/icon-search-task.svg | 36 + icons-svg/_old/icon-send.svg | 31 + icons-svg/_old/icon-simple-view.svg | 32 + icons-svg/_old/icon-sort.svg | 64 ++ icons-svg/_old/icon-support.svg | 35 + icons-svg/_old/icon-task-cancel.svg | 36 + icons-svg/_old/icon-task-comment.svg | 36 + icons-svg/_old/icon-task-delete.svg | 35 + icons-svg/_old/icon-task-done.svg | 36 + icons-svg/_old/icon-task-plus.svg | 36 + icons-svg/_old/icon-task-remark.svg | 36 + icons-svg/_old/icon-task-request.svg | 36 + icons-svg/_old/icon-telephone.svg | 31 + icons-svg/_old/icon-time-load.svg | 36 + icons-svg/_old/icon-timer.svg | 36 + icons-svg/_old/icon-upload.svg | 44 ++ icons-svg/_old/icon-user-solid.svg | 31 + icons-svg/_old/icon-user.svg | 31 + icons-svg/_old/icon-users-solid.svg | 36 + icons-svg/icon-file-archive.svg | 36 + icons-svg/icon-file-audio.svg | 36 + icons-svg/icon-file-code.svg | 34 + icons-svg/icon-file-default.svg | 35 + icons-svg/icon-file-doc.svg | 71 ++ icons-svg/icon-file-dwg.svg | 36 + icons-svg/icon-file-img.svg | 36 + icons-svg/icon-file-pdf.svg | 69 ++ icons-svg/icon-file-ppt.svg | 36 + icons-svg/icon-file-skp.svg | 36 + icons-svg/icon-file-ttf.svg | 69 ++ icons-svg/icon-file-txt.svg | 69 ++ icons-svg/icon-file-video.svg | 36 + icons-svg/icon-file-vsd.svg | 36 + icons-svg/icon-file-xls.svg | 36 + package-lock.json | 19 +- package.json | 1 + quasar.config.ts | 23 +- src/App.vue | 98 ++- src/boot/auth-init.ts | 6 - src/boot/axios.ts | 19 +- src/boot/global-components.ts | 32 +- src/boot/helpers.ts | 87 ++- src/boot/i18n.ts | 18 +- src/boot/telegram-boot.ts | 27 +- .../admin/account-page/optionPayment.vue | 40 - .../admin/account-page/qtyChatCard.vue | 47 -- src/components/admin/accountHelper.vue | 89 --- src/components/admin/companyInfoBlock.vue | 80 -- src/components/admin/login-page/loginLogo.vue | 118 --- src/components/admin/pnPageCard.vue | 23 - src/components/admin/pnScrollList.vue | 132 ---- .../admin/project-page/ProjectPageChats.vue | 256 ------- .../project-page/ProjectPageCompanies.vue | 193 ----- .../admin/project-page/ProjectPageHeader.vue | 202 ----- .../admin/project-page/ProjectPagePersons.vue | 90 --- .../{admin => }/companyInfoPersons.vue | 0 src/components/meetingBlock.vue | 265 +++++++ src/components/{admin => }/meshBackground.vue | 0 src/components/{admin => }/pnAutoAvatar.vue | 3 +- src/components/pnDialogBody.vue | 37 + .../{admin => }/pnImageSelector.vue | 0 src/components/{admin => }/pnOverlay.vue | 0 src/components/pnPageCard.vue | 20 + src/components/pnScrollList.vue | 180 +++++ src/components/pnTaskPriorityIcon.vue | 24 + .../{admin => }/projectInfoBlock.vue | 0 src/components/taskItem.vue | 51 ++ src/css/app.scss | 83 +- src/css/fonts/pn.eot | Bin 0 -> 4740 bytes src/css/fonts/pn.svg | 25 + src/css/fonts/pn.ttf | Bin 0 -> 4596 bytes src/css/fonts/pn.woff | Bin 0 -> 4672 bytes src/i18n/en-US/index.ts | 2 +- src/i18n/ru-RU/index.ts | 2 +- src/layouts/MainLayout.vue | 58 +- src/pages/AccountChangeEmailPage.vue | 17 - src/pages/AccountChangePasswordPage.vue | 17 - src/pages/AccountCreatePage.vue | 17 - src/pages/AccountForgotPasswordPage.vue | 22 - src/pages/AccountPage.vue | 88 --- src/pages/CompanyCreatePage.vue | 39 - src/pages/CompanyInfoPage.vue | 74 -- src/pages/CompanyMaskPage.vue | 157 ---- src/pages/CompanyYourPage.vue | 39 - src/pages/LoginPage.vue | 215 ------ src/pages/{ProjectPage.vue => MainPage.vue} | 62 +- src/pages/MeetingAddPage.vue | 44 ++ src/pages/MeetingEditPage.vue | 236 ++++++ src/pages/MeetingInfoPage.vue | 236 ++++++ src/pages/PersonInfoPage.vue | 113 --- src/pages/PrivacyPage.vue | 22 - src/pages/ProjectCreatePage.vue | 60 -- src/pages/ProjectInfoPage.vue | 75 -- src/pages/ProjectsPage.vue | 233 ------ src/pages/SettingsPage.vue | 41 +- src/pages/SubscribePage.vue | 82 -- src/pages/TaskAddPage.vue | 236 ++++++ src/pages/TaskInfoPage.vue | 93 +++ src/pages/TermsPage.vue | 22 - src/pages/UserInfoPage.vue | 168 ++++ src/pages/main/ChatsPage.vue | 87 +++ src/pages/main/FilesPage.vue | 504 ++++++++++++ src/pages/main/HeaderPage.vue | 170 +++++ src/pages/main/MeetingsPage.vue | 545 +++++++++++++ src/pages/main/TasksPage.vue | 269 +++++++ src/pages/main/UsersPage.vue | 129 ++++ src/router/index.ts | 56 +- src/router/routes.ts | 190 ++--- src/stores/auth.ts | 65 -- src/stores/chats.ts | 58 +- src/stores/companies.ts | 37 - src/stores/files.ts | 51 ++ src/stores/meetings.ts | 86 +++ src/stores/projects.ts | 118 +-- src/stores/settings.ts | 155 ++++ src/stores/tasks.ts | 78 ++ src/stores/textSize.ts | 121 --- src/stores/users.ts | 54 ++ src/types.ts | 57 -- src/types/Chat.ts | 19 + src/types/File.ts | 23 + src/types/Meeting.ts | 22 + src/types/Project.ts | 19 + src/types/Task.ts | 26 + src/types/User.ts | 22 + todo.txt | 103 +-- tsconfig.json | 11 - 203 files changed, 9793 insertions(+), 3960 deletions(-) create mode 100644 icons-svg/_old/icon-3-points.svg create mode 100644 icons-svg/_old/icon-bell-solid.svg create mode 100644 icons-svg/_old/icon-bell.svg create mode 100644 icons-svg/_old/icon-bookmark.svg create mode 100644 icons-svg/_old/icon-calendar.svg create mode 100644 icons-svg/_old/icon-change.svg create mode 100644 icons-svg/_old/icon-clear.svg create mode 100644 icons-svg/_old/icon-clipboard.svg create mode 100644 icons-svg/_old/icon-download.svg create mode 100644 icons-svg/_old/icon-dropdown.svg create mode 100644 icons-svg/_old/icon-exit.svg create mode 100644 icons-svg/_old/icon-eye.svg create mode 100644 icons-svg/_old/icon-file-add.svg create mode 100644 icons-svg/_old/icon-file-archive.svg create mode 100644 icons-svg/_old/icon-file-audio.svg create mode 100644 icons-svg/_old/icon-file-delete.svg create mode 100644 icons-svg/_old/icon-file-doc.svg create mode 100644 icons-svg/_old/icon-file-dwg.svg create mode 100644 icons-svg/_old/icon-file-img.svg create mode 100644 icons-svg/_old/icon-file-lock.svg create mode 100644 icons-svg/_old/icon-file-merge.svg create mode 100644 icons-svg/_old/icon-file-move.svg create mode 100644 icons-svg/_old/icon-file-move.svg.2019_10_02_17_03_29.0.svg create mode 100644 icons-svg/_old/icon-file-pdf.svg create mode 100644 icons-svg/_old/icon-file-ppt.svg create mode 100644 icons-svg/_old/icon-file-rename.svg create mode 100644 icons-svg/_old/icon-file-skp.svg create mode 100644 icons-svg/_old/icon-file-ttf.svg create mode 100644 icons-svg/_old/icon-file-txt.svg create mode 100644 icons-svg/_old/icon-file-types.svg create mode 100644 icons-svg/_old/icon-file-update.svg create mode 100644 icons-svg/_old/icon-file-video.svg create mode 100644 icons-svg/_old/icon-file-vsd.svg create mode 100644 icons-svg/_old/icon-file-xls.svg create mode 100644 icons-svg/_old/icon-file.svg create mode 100644 icons-svg/_old/icon-filter.svg create mode 100644 icons-svg/_old/icon-flag-solid.svg create mode 100644 icons-svg/_old/icon-flag.svg create mode 100644 icons-svg/_old/icon-folder-delete.svg create mode 100644 icons-svg/_old/icon-folder-move.svg create mode 100644 icons-svg/_old/icon-folder-over.svg create mode 100644 icons-svg/_old/icon-folder-plus.svg create mode 100644 icons-svg/_old/icon-folder-rename.svg create mode 100644 icons-svg/_old/icon-folder-update.svg create mode 100644 icons-svg/_old/icon-folder.svg create mode 100644 icons-svg/_old/icon-full-view.svg create mode 100644 icons-svg/_old/icon-gear.svg create mode 100644 icons-svg/_old/icon-info.svg create mode 100644 icons-svg/_old/icon-left-arrow.svg create mode 100644 icons-svg/_old/icon-lock-close.svg create mode 100644 icons-svg/_old/icon-lock-open.svg create mode 100644 icons-svg/_old/icon-logo-2.svg.png create mode 100644 icons-svg/_old/icon-logo.png create mode 100644 icons-svg/_old/icon-logo.svg create mode 100644 icons-svg/_old/icon-mail.svg create mode 100644 icons-svg/_old/icon-messege-2.svg create mode 100644 icons-svg/_old/icon-messege-2.svg.png create mode 100644 icons-svg/_old/icon-messege-3.svg.png create mode 100644 icons-svg/_old/icon-messege-done.svg create mode 100644 icons-svg/_old/icon-messege-second.svg create mode 100644 icons-svg/_old/icon-messege.svg create mode 100644 icons-svg/_old/icon-open-folder.svg create mode 100644 icons-svg/_old/icon-paperclip.svg create mode 100644 icons-svg/_old/icon-pencil.svg create mode 100644 icons-svg/_old/icon-plus.svg create mode 100644 icons-svg/_old/icon-question.svg create mode 100644 icons-svg/_old/icon-question2.svg create mode 100644 icons-svg/_old/icon-right-arrow.svg create mode 100644 icons-svg/_old/icon-save (2).svg create mode 100644 icons-svg/_old/icon-save.svg create mode 100644 icons-svg/_old/icon-search-1.svg create mode 100644 icons-svg/_old/icon-search-file.svg create mode 100644 icons-svg/_old/icon-search-task.svg create mode 100644 icons-svg/_old/icon-send.svg create mode 100644 icons-svg/_old/icon-simple-view.svg create mode 100644 icons-svg/_old/icon-sort.svg create mode 100644 icons-svg/_old/icon-support.svg create mode 100644 icons-svg/_old/icon-task-cancel.svg create mode 100644 icons-svg/_old/icon-task-comment.svg create mode 100644 icons-svg/_old/icon-task-delete.svg create mode 100644 icons-svg/_old/icon-task-done.svg create mode 100644 icons-svg/_old/icon-task-plus.svg create mode 100644 icons-svg/_old/icon-task-remark.svg create mode 100644 icons-svg/_old/icon-task-request.svg create mode 100644 icons-svg/_old/icon-telephone.svg create mode 100644 icons-svg/_old/icon-time-load.svg create mode 100644 icons-svg/_old/icon-timer.svg create mode 100644 icons-svg/_old/icon-upload.svg create mode 100644 icons-svg/_old/icon-user-solid.svg create mode 100644 icons-svg/_old/icon-user.svg create mode 100644 icons-svg/_old/icon-users-solid.svg create mode 100644 icons-svg/icon-file-archive.svg create mode 100644 icons-svg/icon-file-audio.svg create mode 100644 icons-svg/icon-file-code.svg create mode 100644 icons-svg/icon-file-default.svg create mode 100644 icons-svg/icon-file-doc.svg create mode 100644 icons-svg/icon-file-dwg.svg create mode 100644 icons-svg/icon-file-img.svg create mode 100644 icons-svg/icon-file-pdf.svg create mode 100644 icons-svg/icon-file-ppt.svg create mode 100644 icons-svg/icon-file-skp.svg create mode 100644 icons-svg/icon-file-ttf.svg create mode 100644 icons-svg/icon-file-txt.svg create mode 100644 icons-svg/icon-file-video.svg create mode 100644 icons-svg/icon-file-vsd.svg create mode 100644 icons-svg/icon-file-xls.svg delete mode 100644 src/boot/auth-init.ts delete mode 100644 src/components/admin/account-page/optionPayment.vue delete mode 100644 src/components/admin/account-page/qtyChatCard.vue delete mode 100644 src/components/admin/accountHelper.vue delete mode 100644 src/components/admin/companyInfoBlock.vue delete mode 100644 src/components/admin/login-page/loginLogo.vue delete mode 100644 src/components/admin/pnPageCard.vue delete mode 100644 src/components/admin/pnScrollList.vue delete mode 100644 src/components/admin/project-page/ProjectPageChats.vue delete mode 100644 src/components/admin/project-page/ProjectPageCompanies.vue delete mode 100644 src/components/admin/project-page/ProjectPageHeader.vue delete mode 100644 src/components/admin/project-page/ProjectPagePersons.vue rename src/components/{admin => }/companyInfoPersons.vue (100%) create mode 100644 src/components/meetingBlock.vue rename src/components/{admin => }/meshBackground.vue (100%) rename src/components/{admin => }/pnAutoAvatar.vue (88%) create mode 100644 src/components/pnDialogBody.vue rename src/components/{admin => }/pnImageSelector.vue (100%) rename src/components/{admin => }/pnOverlay.vue (100%) create mode 100644 src/components/pnPageCard.vue create mode 100644 src/components/pnScrollList.vue create mode 100644 src/components/pnTaskPriorityIcon.vue rename src/components/{admin => }/projectInfoBlock.vue (100%) create mode 100644 src/components/taskItem.vue create mode 100644 src/css/fonts/pn.eot create mode 100644 src/css/fonts/pn.svg create mode 100644 src/css/fonts/pn.ttf create mode 100644 src/css/fonts/pn.woff delete mode 100644 src/pages/AccountChangeEmailPage.vue delete mode 100644 src/pages/AccountChangePasswordPage.vue delete mode 100644 src/pages/AccountCreatePage.vue delete mode 100644 src/pages/AccountForgotPasswordPage.vue delete mode 100644 src/pages/AccountPage.vue delete mode 100644 src/pages/CompanyCreatePage.vue delete mode 100644 src/pages/CompanyInfoPage.vue delete mode 100644 src/pages/CompanyMaskPage.vue delete mode 100644 src/pages/CompanyYourPage.vue delete mode 100644 src/pages/LoginPage.vue rename src/pages/{ProjectPage.vue => MainPage.vue} (53%) create mode 100644 src/pages/MeetingAddPage.vue create mode 100644 src/pages/MeetingEditPage.vue create mode 100644 src/pages/MeetingInfoPage.vue delete mode 100644 src/pages/PersonInfoPage.vue delete mode 100644 src/pages/PrivacyPage.vue delete mode 100644 src/pages/ProjectCreatePage.vue delete mode 100644 src/pages/ProjectInfoPage.vue delete mode 100644 src/pages/ProjectsPage.vue delete mode 100644 src/pages/SubscribePage.vue create mode 100644 src/pages/TaskAddPage.vue create mode 100644 src/pages/TaskInfoPage.vue delete mode 100644 src/pages/TermsPage.vue create mode 100644 src/pages/UserInfoPage.vue create mode 100644 src/pages/main/ChatsPage.vue create mode 100644 src/pages/main/FilesPage.vue create mode 100644 src/pages/main/HeaderPage.vue create mode 100644 src/pages/main/MeetingsPage.vue create mode 100644 src/pages/main/TasksPage.vue create mode 100644 src/pages/main/UsersPage.vue delete mode 100644 src/stores/auth.ts delete mode 100644 src/stores/companies.ts create mode 100644 src/stores/files.ts create mode 100644 src/stores/meetings.ts create mode 100644 src/stores/settings.ts create mode 100644 src/stores/tasks.ts delete mode 100644 src/stores/textSize.ts create mode 100644 src/stores/users.ts delete mode 100644 src/types.ts create mode 100644 src/types/Chat.ts create mode 100644 src/types/File.ts create mode 100644 src/types/Meeting.ts create mode 100644 src/types/Project.ts create mode 100644 src/types/Task.ts create mode 100644 src/types/User.ts diff --git a/backend/apps/admin.js b/backend/apps/admin.js index 854a091..0cb3013 100644 --- a/backend/apps/admin.js +++ b/backend/apps/admin.js @@ -1,25 +1,46 @@ const crypto = require('crypto') const express = require('express') -const multer = require('multer') const db = require('../include/db') const bot = require('./bot') const fs = require('fs') -const cookieParser = require('cookie-parser') const app = express.Router() -const upload = multer({ - storage: multer.memoryStorage(), - limits: { - fileSize: 1_000_000 // 1mb - } -}) const sessions = {} +const cache = { + // email -> code + register: {}, + upgrade: {}, + recovery: {}, + 'change-password': {}, + 'change-email': {}, + 'change-email2': {} +} + +function checkEmail(email){ + return String(email) + .toLowerCase() + .match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) +} + +function sendEmail(email, subject, message) { + console.log(`${email} --> ${subject}: ${message}`) +} + +function checkPassword(password) { + return password.length >= 8 +} app.use((req, res, next) => { - if (req.path == '/customer/login' || - req.path == '/customer/register' || - req.path == '/customer/activate') + const public = [ + '/auth/email', + '/auth/telegram', + '/auth/email/register', + '/auth/email/recovery', + '/auth/logout' + ] + + if (public.includes(req.path)) return next() const asid = req.query.asid || req.cookies.asid @@ -31,105 +52,241 @@ app.use((req, res, next) => { next() }) -// CUSTOMER -app.post('/customer/login', (req, res, next) => { +// AUTH +function createSession(req, res, customer_id) { + if (!customer_id) + throw Error('AUTH_ERROR::500') + + res.locals.customer_id = customer_id + const asid = crypto.randomBytes(64).toString('hex') + req.session = sessions[asid] = {asid, customer_id } + res.setHeader('Set-Cookie', [`asid=${asid};httpOnly;path=/api/admin`]) +} + +app.post('/auth/email', (req, res, next) => { res.locals.email = req.body?.email res.locals.password = req.body?.password - let customer_id = db - .prepare(` - select id - from customers - where is_active = 1 and ( - email is not null and email = :email and password is not null and password = :password or - email is null and password is null and telegram_user_id = :telegram_id - ) - `) + const customer_id = db + .prepare(`select id from customers where email = :email and password is not null and password = :password `) .pluck(true) .get(res.locals) - if (!customer_id && !res.locals.email && !res.locals.password) { - customer_id = db - .prepare(`insert into customers (telegram_user_id, is_active) values (:telegram_id, 1) returning id`) - .safeIntegers(true) - .pluck(true) - .get(res.locals) - } - if (!customer_id) throw Error('AUTH_ERROR::401') - res.locals.customer_id = customer_id - db - .prepare(`update customers set telegram_user_id = :telegram_id where id = :customer_id and email is not null`) - .run(res.locals) - - const asid = crypto.randomBytes(64).toString('hex') - req.session = sessions[asid] = {asid, customer_id } - res.setHeader('Set-Cookie', [`asid=${asid};httpOnly;path=/api/admin`]) + createSession(req, res, customer_id) + res.status(200).json({success: true}) +}) + +app.post('/auth/telegram', (req, res, next) => { + let customer_id = db + .prepare(`select id from customers where telegram_id = :telegram_id`) + .pluck(true) + .get(res.locals) || db + .prepare(`replace into customers (telegram_id) values (:telegram_id) returning id`) + .pluck(true) + .get(res.locals) + + createSession(req, res, customer_id) + res.status(200).json({success: true}) +}) + +/* + Регистрация нового клиента/Перевод авторизации с TG на email выполняется за ТРИ последовательных вызова + 1. Отравляется email. Если email корректный и уже неиспользуется, то сервер возвращает ОК и на указанный email отправляется код. + 2. Отправляется email + код из письма. Если указан корректный код, то сервер отвечает ОК. + 3. Отправляется email + код из письма + желаемый пароль. Если все ОК, то сервер создает учетную запись и возвращает ОК. +*/ +app.post('/auth/email/:action(register|upgrade)', (req, res, next) => { + const email = String(req.body.email ?? '').trim() + const code = String(req.body.code ?? '').trim() + const password = String(req.body.password ?? '').trim() + const action = req.params.action + + const stepNo = email && !code ? 1 : email && code && !password ? 2 : email && code && password ? 3 : -1 + if (stepNo == -1) + throw Error('BAD_STEP::400') + + if (stepNo == 1) { + if (!checkEmail(email)) + throw Error('INCORRECT_EMAIL::400') + + const customer_id = db + .prepare('select id from customers where email = :email') + .pluck(true) + .get({email}) + + if (customer_id) + throw Error('USED_EMAIL::400') + + const code = Math.random().toString().substr(2, 4) + cache[action][email] = code + sendEmail(email, action.toUpperCase(), `${email} => ${code}`) + } + + if (stepNo == 2) { + if (cache[action][email] != code) + throw Error('INCORRECT_CODE::400') + } + + if (stepNo == 3) { + if (!checkPassword(password)) + throw Error('INCORRECT_PASSWORD::400') + + const query = action == 'register' ? 'insert into customers (email, password) values (:email, :password)' : + 'update customers set email = :email, password = :password where id = :id' + db.prepare(query).run({email, password, id: res.locals.customer_id}) + + delete cache[action][email] + } + + res.status(200).json({success: true}) +}) + + +/* + Смена email выполняется за ЧЕТЫРЕ последовательных вызовов + 1. Отравляется пустой закпрос. Сервер на email пользователя из базы отправляет код. + 2. Отправляется код из письма. Если указан корректный код, то сервер отвечает ОК. + 3. Отправляется код из письма + новый email. Сервер отправляет код2 на новый email. + 4. Отправлются оба кода и новый email. Если они проходят проверку, то сервер меняет email пользователя на новый и возвращает ОК. +*/ +app.post('/auth/email/change-email', (req, res, next) => { + const email2 = String(req.body.email ?? '').trim() + const code = String(req.body.code ?? '').trim() + const code2 = String(req.body.code2 ?? '').trim() + + const email = db + .prepare('select email from customers where id = :customer_id') + .pluck(true) + .get(res.locals) + + const stepNo = !code ? 1 : code && !email ? 2 : code && email && !code2 ? 3 : code && email && code2 ? 4 : -1 + if (stepNo == -1) + throw Error('BAD_STEP::400') + + if (stepNo == 1) { + const code = Math.random().toString().substr(2, 4) + cache['change-email'][email] = code + sendEmail(email, 'CHANGE-EMAIL', `${email} => ${code}`) + } + + if (stepNo == 2) { + if (cache['change-email'][email] != code) + throw Error('INCORRECT_CODE::400') + } + + if (stepNo == 3) { + if (!checkEmail(email2)) + throw Error('INCORRECT_EMAIL::400') + + const code2 = Math.random().toString().substr(2, 4) + cache['change-email2'][email2] = code2 + sendEmail(email2, 'CHANGE-EMAIL2', `${email2} => ${code2}`) + } + + if (stepNo == 4) { + if (cache['change-email'][email] != code || cache['change-email2'][email2] != code2) + throw Error('INCORRECT_CODE::400') + + const info = db + .prepare('update customers set email = :email where id = :customer_id') + .run(res.locals) + + if (info.changes == 0) + throw Error('BAD_REQUEST::400') + + delete cache['change-email'][email] + delete cache['change-email2'][email2] + } res.status(200).json({success: true}) }) -app.get('/customer/logout', (req, res, next) => { - delete sessions[req.session.asid] - res.setHeader('Set-Cookie', [`asid=; expired; httpOnly`]) +/* + Смена пароля/восстановление доступа выполняется за ТРИ последовательных вызова + 1. Отравляется пустой закпрос для смены запоса и email, в случае восстановления доступа. Сервер на email отправляет код. + 2. Отправляется email + код из письма. Если указан корректный код, то сервер отвечает ОК. + 3. Отправляется email + код из письма + новый пароль. Сервер изменяет пароль и возвращает ОК. +*/ +app.post('/auth/email/:action(change-password|recovery)', (req, res, next) => { + const code = String(req.body.code ?? '').trim() + const password = String(req.body.password) + const action = req.params.action + + const email = action == 'change-password' ? db + .prepare('select email from customers where id = :customer_id') + .pluck(true) + .get(res.locals) : + String(req.body.email ?? '').trim() + + const stepNo = action == 'change-password' ? + (!code && !password ? 1 : code && !password ? 2 : code && password ? 3 : -1) : + (!email && !code && !password ? 1 : email && code && !password ? 2 : email && code && password ? 3 : -1) + if (stepNo == -1) + throw Error('BAD_STEP::400') + + if (stepNo == 1) { + if (!checkEmail(email)) + throw Error('INCORRECT_EMAIL::400') + + const code = Math.random().toString().substr(2, 4) + cache[action][email] = code + sendEmail(email, action.toUpperCase(), `${email} => ${code}`) + } + + if (stepNo == 2) { + if (cache[action][email] != code) + throw Error('INCORRECT_CODE::400') + } + + if (stepNo == 3) { + if (cache[action][email] != code) + throw Error('INCORRECT_CODE::400') + + if (!checkPassword(password)) + throw Error('INCORRECT_PASSWORD::400') + + const info = db + .prepare('update customers set password = :password where email = :email') + .run({ email, password }) + + if (info.changes == 0) + throw Error('BAD_REQUEST::400') + + delete cache[action][email] + } + res.status(200).json({success: true}) }) -app.post('/customer/register', (req, res, next) => { - const email = String(req.body.email).trim() - const password = String(req.body.password).trim() +app.get('/auth/logout', (req, res, next) => { + if (req.session?.asid) + delete sessions[req.session.asid] - const validateEmail = email => String(email).toLowerCase().match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/) - if (!validateEmail(email)) - throw Error('INCORRECT_EMAIL::400') - - if (!password) - throw Error('EMPTY_PASSWORD::400') - - const row = db - .prepare('select id from customers where email = :email') - .run({email}) - if (row) - throw Error('DUPLICATE_EMAIL::400') - - const key = crypto.randomBytes(32).toString('hex') - const info = db - .prepare('insert into customers (email, password, activation_key) values (:email, :password, :key)') - .run({email, password, key}) - - // To-Do: SEND MAIL - console.log(`http://127.0.0.1:3000/api/customer/activate?key=${key}`) - res.status(200).json({success: true, data: key}) -}) - -app.get('/customer/activate', (req, res, next) => { - const row = db - .prepare('update customers set is_active = 1 where activation_key = :key returning id') - .get({key: req.query.key}) - - if (!row || !row.id) - throw Error('BAD_ACTIVATION_KEY::400') - - res.status(200).json({success: true}) + res.setHeader('Set-Cookie', [`asid=; expired; httpOnly;path=/api/admin`]) + res.status(200).json({success: true}) }) +// CUSTOMER app.get('/customer/profile', (req, res, next) => { const row = db .prepare(` - select id, name, email, plan, coalesce(json_balance, '{}') json_balance, coalesce(json_company, '{}') json_company, upload_group_id + select id, name, email, plan, + coalesce(json_balance, '{}') json_balance, coalesce(json_company, '{}') json_company, + upload_chat_id, generate_key(-id, :time) upload_token from customers - where id = :customer_id and is_active = 1 + where id = :customer_id `) .get(res.locals) - if (row?.upload_group_id) { - row.upload_group = db - .prepare(`select id, name, telegram_id from groups where id = :group_id and project_id is null`) + if (row?.upload_chat_id) { + row.upload_chat = db + .prepare(`select id, name, telegram_id from chats where id = :chat_id and project_id is null`) .safeIntegers(true) - .get({ group_id: row.upload_group_id}) - delete row.upload_group_id + .get({ chat_id: row.upload_chat_id}) + delete row.upload_chat_id } for (const key in row) { @@ -145,6 +302,8 @@ app.get('/customer/profile', (req, res, next) => { app.put('/customer/profile', (req, res, next) => { if (req.body.company instanceof Object) req.body.json_company = JSON.stringify(req.body.company) + else + delete req.body?.json_company const info = db .prepareUpdate( @@ -160,96 +319,91 @@ app.put('/customer/profile', (req, res, next) => { res.status(200).json({success: true}) }) -// PROJECT -app.get('/project', (req, res, next) => { - const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : '' +app.get('/customer/settings', (req, res, next) => { + const row = db + .prepare(`select coalesce(json_settings, '{}') from customers where id = :customer_id`) + .pluck(true) + .get(res.locals) + + res.status(200).json({success: true, data: JSON.parse(row)}) +}) - const rows = db +app.put('/customer/settings', (req, res, next) => { + res.locals.json_settings = JSON.stringify(req.body || {}) + + db + .prepare(`update customers set json_settings = :json_settings where id = :customer_id`) + .run(res.locals) + + res.status(200).json({success: true}) +}) + +// PROJECT +function getProject(id, customer_id) { + const row = db .prepare(` - select id, name, description, logo - from projects - where customer_id = :customer_id ${where} and is_deleted <> 1 + select id, name, description, logo, is_logo_bg, is_archived, + (select count(*) from chats where project_id = p.id) chat_count, + (select count(distinct user_id) from chat_users where chat_id in (select id from chats where project_id = p.id)) user_count + from projects p + where customer_id = :customer_id and p.id = :id + order by name + `) + .get({id, customer_id}) + + if (!row) + throw Error('NOT_FOUND::404') + + row.is_archived = Boolean(row.is_archived) + row.is_logo_bg = Boolean(row.is_logo_bg) + + return row +} + +app.get('/project', (req, res, next) => { + const data = db + .prepare(` + select id, name, description, logo, is_logo_bg, is_archived, + (select count(*) from chats where project_id = p.id) chat_count, + (select count(distinct user_id) from chat_users where chat_id in (select id from chats where project_id = p.id)) user_count + from projects p + where customer_id = :customer_id order by name `) .all(res.locals) - if (where && rows.length == 0) - throw Error('NOT_FOUND::404') + data.forEach(row => { + row.is_archived = Boolean(row.is_archived) + row.is_logo_bg = Boolean(row.is_logo_bg) + }) - res.status(200).json({success: true, data: where ? rows[0] : rows}) -}) - -app.get('/project/:pid(\\d+)', (req, res, next) => { - res.redirect(req.baseUrl + `/project?id=${req.params.pid}`) + res.status(200).json({success: true, data}) }) app.post('/project', (req, res, next) => { res.locals.name = req.body?.name res.locals.description = req.body?.description res.locals.logo = req.body?.logo + res.locals.is_logo_bg = 'is_logo_bg' in req.body ? +req.body.is_logo_bg : undefined const id = db .prepare(` - insert into projects (customer_id, name, description, logo) - values (:customer_id, :name, :description, :logo) + insert into projects (customer_id, name, description, logo, is_logo_bg) + values (:customer_id, :name, :description, :logo, :is_logo_bg) returning id `) .pluck(true) .get(res.locals) - res.status(200).json({success: true, data: id}) + const data = getProject(id, res.locals.customer_id) + res.status(200).json({success: true, data}) }) -app.put('/project/:pid(\\d+)', (req, res, next) => { - res.locals.id = req.params.pid - res.locals.name = req.body?.name - res.locals.description = req.body?.description - res.locals.logo = req.body?.logo - - const info = db - .prepareUpdate( - 'projects', - ['name', 'description', 'logo'], - res.locals, - ['id', 'customer_id']) - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true}) -}) - -app.delete('/project/:pid(\\d+)', async (req, res, next) => { - res.locals.id = req.params.pid - - const info = db - .prepare('update projects set id_deleted = 1 where id = :id and customer_id = :customer_id') - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - const groupIds = db - .prepare(`select id from groups where project_id = :id`) - .pluck(true) - .all(res.locals) - - for (const groupId of groupIds) { - await bot.sendMessage(groupId, 'Проект удален') - await bot.leaveGroup(groupId) - } - - db.prepare(`updates groups set project_id = null where id in (${ groupIds.join(', ')})`).run() - - res.status(200).json({success: true}) -}) - -app.use ('/project/:pid(\\d+)/*', (req, res, next) => { +app.use ('(/project/:pid(\\d+)/*|/project/:pid(\\d+))', (req, res, next) => { res.locals.project_id = parseInt(req.params.pid) const row = db - .prepare('select 1 from projects where id = :project_id and customer_id = :customer_id and is_deleted <> 1') + .prepare('select 1 from projects where id = :project_id and customer_id = :customer_id and is_archived <> 1') .get(res.locals) if (!row) @@ -258,55 +412,134 @@ app.use ('/project/:pid(\\d+)/*', (req, res, next) => { next() }) -// USER -app.get('/project/:pid(\\d+)/user', (req, res, next) => { - const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : '' +app.get('/project/:pid(\\d+)', (req, res, next) => { + const data = getProject(req.params.pid, res.locals.customer_id) + res.status(200).json({success: true, data}) +}) - const rows = db +app.put('/project/:pid(\\d+)', (req, res, next) => { + res.locals.id = req.params.pid + res.locals.name = req.body?.name + res.locals.description = req.body?.description + res.locals.logo = req.body?.logo + res.locals.is_logo_bg = 'is_logo_bg' in req.body ? +req.body.is_logo_bg : undefined + + const info = db + .prepareUpdate( + 'projects', + ['name', 'description', 'logo', 'is_logo_bg'], + res.locals, + ['id', 'customer_id']) + .run(res.locals) + + if (info.changes == 0) + throw Error('NOT_FOUND::404') + + const data = getProject(req.params.pid, res.locals.customer_id) + res.status(200).json({success: true, data}) +}) + +app.put('/project/:pid(\\d+)/:action(archive|restore)', async (req, res, next) => { + res.locals.id = req.params.pid + res.locals.is_archived = +(req.params.action == 'archive') + + const info = db + .prepare(` + update projects + set is_archived = :is_archived + where id = :id and customer_id = :customer_id and coalesce(is_archived, 0) = not :is_archived + `) + .run(res.locals) + + if (info.changes == 0) + throw Error('BAD_REQUEST::400') + + const chatIds = db + .prepare(`select id from chats where project_id = :id`) + .pluck(true) + .all(res.locals) + + for (const chatId of chatIds) { + await bot.sendMessage(chatId, res.locals.is_archived ? 'Проект помещен в архив. Отслеживание сообщений прекращено.' : 'Проект восстановлен из архива.') + } + + const data = getProject(req.params.pid, res.locals.customer_id) + res.status(200).json({success: true, data}) +}) + +// USER +function getUser(id, project_id) { + const row = db .prepare(` select u.id, u.telegram_id, u.firstname, u.lastname, u.username, u.photo, - ud.fullname, ud.role, ud.department, ud.is_blocked + ud.fullname, ud.email, ud.phone, ud.role, ud.department, ud.is_blocked, + cu.company_id from users u left join user_details ud on u.id = ud.user_id and ud.project_id = :project_id + left join company_users cu on u.id = cu.user_id + where id = :id + `) + .safeIntegers(true) + .all({id, project_id}) + + if (!row) + throw Error('NOT_FOUND::404') + + row.is_blocked = Boolean(row.is_blocked) + + return row +} + +app.get('/project/:pid(\\d+)/user', (req, res, next) => { + const data = db + .prepare(` + select u.id, u.telegram_id, u.firstname, u.lastname, u.username, u.photo, + ud.fullname, ud.email, ud.phone, ud.role, ud.department, ud.is_blocked, + cu.company_id + from users u + left join user_details ud on u.id = ud.user_id and ud.project_id = :project_id + left join company_users cu on u.id = cu.user_id where id in ( select user_id - from group_users - where group_id in (select id from groups where project_id = :project_id) - ) ${where} + from chat_users + where chat_id in (select id from chats where project_id = :project_id) + ) `) .safeIntegers(true) .all(res.locals) - if (where && rows.length == 0) - throw Error('NOT_FOUND::404') + data.forEach(row => { + row.is_blocked = Boolean(row.is_blocked) + }) - res.status(200).json({success: true, data: where ? rows[0] : rows}) + res.status(200).json({success: true, data}) }) app.get('/project/:pid(\\d+)/user/:uid(\\d+)', (req, res, next) => { - res.redirect(req.baseUrl + `/project/${req.params.pid}/user?id=${req.params.uid}`) + const data = getUser(req.params.uid, req.params.pid) + res.status(200).json({success: true, data}) }) app.put('/project/:pid(\\d+)/user/:uid(\\d+)', (req, res, next) => { res.locals.user_id = parseInt(req.params.uid) res.locals.fullname = req.body?.fullname + res.locals.email = req.body?.email + res.locals.phone = req.body?.phone res.locals.role = req.body?.role res.locals.department = req.body?.department - res.locals.is_blocked = req.body?.is_blocked + res.locals.is_blocked = 'is_blocked' in req.body ? +req.body.is_blocked : undefined const info = db - .prepareUpdate('user_details', - ['fullname', 'role', 'department', 'is_blocked'], + .prepareUpsert('user_details', + ['fullname', 'email', 'phone', 'role', 'department', 'is_blocked'], res.locals, ['user_id', 'project_id'] ) - .all(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') + .run(res.locals) - res.status(200).json({success: true}) + const data = getUser(req.params.uid, req.params.pid) + res.status(200).json({success: true, data}) }) app.get('/project/:pid(\\d+)/token', (req, res, next) => { @@ -324,33 +557,47 @@ app.get('/project/:pid(\\d+)/token', (req, res, next) => { }) // COMPANY -app.get('/project/:pid(\\d+)/company', (req, res, next) => { - const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : '' - - const rows = db +function getCompany(id, project_id) { + const row = db .prepare(` - select id, name, email, phone, description, logo, + select id, name, address, email, phone, site, description, logo, (select json_group_array(user_id) from company_users where company_id = c.id) users from companies c - where project_id = :project_id ${where} + where c.id = :id and project_id = :project_id + order by name + `) + .get({id, project_id}) + + if (!row) + throw Error('NOT_FOUND::404') + + return row +} + +app.get('/project/:pid(\\d+)/company', (req, res, next) => { + const data = db + .prepare(` + select id, name, address, email, phone, site, description, logo, + (select json_group_array(user_id) from company_users where company_id = c.id) users + from companies c + where project_id = :project_id order by name `) .all(res.locals) - rows.forEach(row => row.users = JSON.parse(row.users || '[]')) + data.forEach(row => row.users = JSON.parse(row.users || '[]')) - if (where && rows.length == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true, data: where ? rows[0] : rows}) + res.status(200).json({success: true, data}) }) app.get('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => { - res.redirect(req.baseUrl + `/project/${req.params.pid}/company?id=${req.params.cid}`) + const data = getCompany(req.params.cid, req.params.pid) + res.status(200).json({success: true, data}) }) app.post('/project/:pid(\\d+)/company', (req, res, next) => { res.locals.name = req.body?.name + res.locals.address = req.body?.address res.locals.email = req.body?.email res.locals.phone = req.body?.phone res.locals.site = req.body?.site @@ -359,28 +606,31 @@ app.post('/project/:pid(\\d+)/company', (req, res, next) => { const id = db .prepare(` - insert into companies (project_id, name, email, phone, site, description, logo) - values (:project_id, :name, :email, :phone, :site, :description, :logo) + insert into companies (project_id, name, address, email, phone, site, description, logo) + values (:project_id, :name, :address, :email, :phone, :site, :description, :logo) returning id `) - .pluck(res.locals) + .pluck(true) .get(res.locals) - res.status(200).json({success: true, data: id}) + const data = getCompany(id, req.params.pid) + res.status(200).json({success: true, data}) }) app.put('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => { res.locals.id = parseInt(req.params.cid) res.locals.name = req.body?.name + res.locals.address = req.body?.address res.locals.email = req.body?.email res.locals.phone = req.body?.phone res.locals.site = req.body?.site res.locals.description = req.body?.description + res.locals.logo = req.body?.logo const info = db .prepareUpdate( 'companies', - ['name', 'email', 'phone', 'site', 'description'], + ['name', 'address', 'email', 'phone', 'site', 'description', 'logo'], res.locals, ['id', 'project_id']) .run(res.locals) @@ -388,11 +638,12 @@ app.put('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => { if (info.changes == 0) throw Error('NOT_FOUND::404') - res.status(200).json({success: true}) + const data = getCompany(req.params.cid, req.params.pid) + res.status(200).json({success: true, data}) }) app.delete('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => { - res.locals.company_id = parseInt(req.params.cid) + res.locals.company_id = req.params.cid const info = db .prepare(`delete from companies where id = :company_id and project_id = :project_id`) @@ -401,42 +652,7 @@ app.delete('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => { if (info.changes == 0) throw Error('NOT_FOUND::404') - res.status(200).json({success: true}) -}) - -app.get('/project/:pid(\\d+)/group', (req, res, next) => { - const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : '' - - const rows = db - .prepare(` - select id, name, telegram_id, is_channel, user_count, bot_can_ban - from groups - where project_id = :project_id ${where} - `) - .all(res.locals) - - if (where && rows.length == 0) - throw Error('NOT_FOUND::404') - - res.status(200).json({success: true, data: where ? rows[0] : rows}) -}) - -app.get('/project/:pid(\\d+)/group/:gid(\\d+)', (req, res, next) => { - res.redirect(req.baseUrl + `/project/${req.params.pid}/group?id=${req.params.uid}`) -}) - -app.delete('/project/:pid(\\d+)/group/:gid(\\d+)', async (req, res, next) => { - res.locals.group_id = parseInt(req.params.gid) - const info = db - .prepare(`update groups set project_id = null where id = :group_id and project_id = :project_id`) - .run(res.locals) - - if (info.changes == 0) - throw Error('NOT_FOUND::404') - - await bot.sendMessage(res.locals.group_id, 'Группа удалена из проекта') - - res.status(200).json({success: true}) + res.status(200).json({success: true, data: {id: req.params.cid}}) }) app.put('/project/:pid(\\d+)/company/:cid(\\d+)/user', (req, res, next) => { @@ -456,8 +672,8 @@ app.put('/project/:pid(\\d+)/company/:cid(\\d+)/user', (req, res, next) => { let rows = db .prepare(` select user_id - from group_users - where group_id in (select id from groups where project_id = :project_id) + from chat_users + where chat_id in (select id from chats where project_id = :project_id) `) .pluck(true) // .raw? .get(res.locals) @@ -478,7 +694,6 @@ app.put('/project/:pid(\\d+)/company/:cid(\\d+)/user', (req, res, next) => { if (user_ids.some(user_id => !rows.contains(user_id))) throw Error('USED_MEMBER::400') - db .prepare(`delete from company_users where company_id = :company_id`) .run(res.locals) @@ -493,4 +708,59 @@ app.put('/project/:pid(\\d+)/company/:cid(\\d+)/user', (req, res, next) => { res.status(200).json({success: true}) }) +// CHATS +function getChat(id, project_id) { + const row = db + .prepare(` + select id, name, telegram_id, is_channel, description, logo, user_count, bot_can_ban + from chats c + where c.id = :id and project_id = :project_id + `) + .all({id, project_id}) + + if (!row) + throw Error('NOT_FOUND::404') + + row.is_channel = Boolean(row.is_channel) + row.bot_can_ban = Boolean(row.bot_can_ban) + + return row +} + +app.get('/project/:pid(\\d+)/chat', (req, res, next) => { + const data = db + .prepare(` + select id, name, telegram_id, is_channel, description, logo, user_count, bot_can_ban + from chats + where project_id = :project_id + `) + .all(res.locals) + + data.forEach(row => { + row.is_channel = Boolean(row.is_channel) + row.bot_can_ban = Boolean(row.bot_can_ban) + }) + + res.status(200).json({success: true, data}) +}) + +app.get('/project/:pid(\\d+)/chat/:gid(\\d+)', (req, res, next) => { + const data = getChat(req.params.gid, req.params.pid) + res.status(200).json({success: true, data}) +}) + +app.delete('/project/:pid(\\d+)/chat/:gid(\\d+)', async (req, res, next) => { + res.locals.chat_id = req.params.gid + const info = db + .prepare(`update chats set project_id = null where id = :chat_id and project_id = :project_id`) + .run(res.locals) + + if (info.changes == 0) + throw Error('NOT_FOUND::404') + + await bot.sendMessage(res.locals.chat_id, 'Чат удален из проекта') + + res.status(200).json({success: true, data: {id: req.params.gid}}) +}) + module.exports = app \ No newline at end of file diff --git a/backend/apps/bot.js b/backend/apps/bot.js index 963680c..0f84d29 100644 --- a/backend/apps/bot.js +++ b/backend/apps/bot.js @@ -1,19 +1,14 @@ -const util = require('util') -const crypto = require('crypto') -const EventEmitter = require('events') -const fs = require('fs') const db = require('../include/db') const { Api, TelegramClient } = require('telegram') const { StringSession } = require('telegram/sessions') -const { NewMessage } = require('telegram/events') const { Button } = require('telegram/tl/custom/button') const { CustomFile } = require('telegram/client/uploads') -// const session = new StringSession('1AgAOMTQ5LjE1NC4xNjcuNTABuxdIxmjimA0hmWpdrlZ4Fo7uoIGU4Bu9+G5QprS6zdtyeMfcssWEZp0doLRX/20MomQyF4Opsos0El0Ifj5aiNgg01z8khMLMeT98jS+1U/sh32p3GxZfxyXSxX1bD0NLRaXnqVyNNswYqRZPhboT28NMjDqwlz0nrW9rge+QMJDL7jIkXgSs+cmJBINiqsEI8jWjXmc8TU/17gngtjUHRf5kRM4y5gsNC4O8cF5lcHRx0G/U5ZVihTID8ItQ6EdEHjz6e4XErbVOJ81PfYkqEoPXVvkEmRM0/VbvCzFfixfas4Vzczfn98OHLd8P2MXcgokZ2rppvIV3fQXOHxJbA0=') -const session = new StringSession('') - +let session let client +let BOT_ID +const BOT_NAME = 'ready_or_not_2025_bot' function registerUser (telegramId) { db @@ -78,121 +73,125 @@ async function updateUserPhoto (userId, data) { db .prepare(`update users set photo_id = :photo_id, photo = :photo where id = :user_id`) .safeIntegers(true) - .run({ user_id: userId, photo_id: photoId, photo: file.toString('base64') }) + .run({ user_id: userId, photo_id: photoId, photo: 'data:image/jpg;base64,' + file.toString('base64') }) } -async function registerGroup (telegramId, isChannel) { - db - .prepare(`insert or ignore into groups (telegram_id, is_channel) values (:telegram_id, :is_channel)`) +async function registerChat (telegramId, isChannel) { + const chat = db + .prepare(`select id, name, is_channel, access_hash from chats where telegram_id = :telegram_id`) .safeIntegers(true) - .run({ telegram_id: telegramId, is_channel: +isChannel }) + .get({telegram_id: telegramId}) - const row = db - .prepare(`select id, name from groups where telegram_id = :telegram_id`) - .safeIntegers(true) - .get({telegram_id: telegramId}) - - if (!row?.name) { - const entity = isChannel ? { channelId: telegramId } : { chatId: telegramId } - const group = await client.getEntity(isChannel ? new Api.PeerChannel(entity) : new Api.PeerChat(entity)) - - db - .prepare(`update groups set name = :name where id = :group_id`) - .run({ group_id: row.id, name: group.title }) - } - - return row.id -} - -async function attachGroup(groupId, isChannel, projectId) { - const info = db - .prepare(`update groups set project_id = :project_id where id = :group_id and coalesce(project_id, 1) = 1`) - .run({ group_id: groupId, project_id: projectId }) - - if (info.changes == 1) { - const inputPeer = isChannel ? - new Api.InputPeerChannel({ channelId: tgGroupId }) : - new Api.InputPeerChat({ chatlId: tgGroupId }) - - const query = `select (select name from customers where id = p.customer_id) || ' >> ' || p.name from projects p where id = :project_id` - const message = db - .prepare(query) - .pluck(true) - .get({project_id: projectId}) - if (message) - await client.sendMessage(inputPeer, {message}) - } - - return info.changes == 1 -} - -async function onGroupAttach (tgGroupId, isChannel) { - const projectId = db - .prepare(`select project_id from groups where telegram_id = :telegram_id`) - .safeIntegers(true) - .pluck(true) - .get({ telegram_id: tgGroupId }) - - const entity = isChannel ? { channelId: tgGroupId } : { chatId: tgGroupId } - const inputPeer = await client.getEntity( isChannel ? + if (chat && chat.access_hash && chat.is_channel == isChannel && chat.name) + return chat.id + + const entity = isChannel ? { channelId: telegramId } : { chatId: telegramId } + const tgChat = await client.getEntity( isChannel ? new Api.InputPeerChannel(entity) : new Api.InputPeerChat(entity) ) - - const resultBtn = await client.sendMessage(inputPeer, { - message: 'ReadyOrNot', - buttons: client.buildReplyMarkup([[Button.url('Открыть проект', 'https://t.me/ready_or_not_2025_bot/userapp?startapp=user_' + projectId)]]) + + const chatId = db + .prepare(`replace into chats (telegram_id, is_channel, access_hash, name) values (:telegram_id, :is_channel, :access_hash, :name) returning id`) + .safeIntegers(true) + .pluck(true) + .get({ + telegram_id: telegramId, + is_channel: +isChannel, + access_hash: tgChat.accessHash.value, + name: tgChat.title + }) + + await updateChat(chatId) + + return chatId +} + +async function updateChat (chatId) { + const chat = db + .prepare(`select id, telegram_id, access_hash, is_channel from chats where id = :id`) + .safeIntegers(true) + .get({id: chatId}) + + const peer = chat.is_channel ? + new Api.InputPeerChannel({ channelId: chat.telegram_id, accessHash: chat.access_hash }) : + new Api.InputPeerChat({ chatId: chat.telegram_id, accessHash: chat.access_hash }) + + const data = chat.is_channel ? + await client.invoke(new Api.channels.GetFullChannel({ channel: peer })) : + await client.invoke(new Api.messages.GetFullChat({ chatId: chat.telegram_id, accessHash: chat.access_hash })) + + const file = data?.fullChat?.chatPhoto ? await client.downloadFile(new Api.InputPeerPhotoFileLocation({ peer, photoId: data.fullChat.chatPhoto?.id }, {})) : null + logo = file ? 'data:image/jpg;base64,' + file.toString('base64') : null + + db + .prepare(`update chats set description = :description, logo = :logo, user_count = :user_count, last_update_time = :last_update_time where id = :id`) + .safeIntegers(true) + .run({ + id: chatId, + description: data.fullChat.about, + logo, + user_count: data.fullChat.participantsCount - (data.users || []).filter(user => user.bot).length, + last_update_time: Math.floor(Date.now() / 1000) + }) +} + +async function attachChat(chatId, projectId) { + const chat = db + .prepare(`update chats set project_id = :project_id where id = :chat_id returning telegram_id, access_hash, is_channel`) + .safeIntegers(true) + .get({ chat_id: chatId, project_id: projectId }) + + if (!chat.telegram_id) + return console.error('Can\'t attach chat: ' + chatId + ' to project: ' + projectId) + + const peer = chat.is_channel ? + new Api.InputPeerChannel({ channelId: chat.telegram_id, accessHash: chat.access_hash }) : + new Api.InputPeerChat({ chatId: chat.telegram_id, accessHash: chat.access_hash }) + + const message = db + .prepare(`select (select name from customers where id = p.customer_id) || ' >> ' || p.name from projects p where id = :project_id`) + .pluck(true) + .get({project_id: projectId}) + + const resultBtn = await client.sendMessage(peer, { + message, + buttons: client.buildReplyMarkup([[Button.url('Открыть проект', `https://t.me/${BOT_NAME}/userapp?startapp=` + projectId)]]) }) await client.invoke(new Api.messages.UpdatePinnedMessage({ - peer: inputPeer, + peer, id: resultBtn.id, unpin: false })) - -//fs.appendFileSync('./1.log', '\n>' + tgGroupId + ':' + isChannel + '<\n') } -async function reloadGroupUsers(groupId, onlyReset) { +async function reloadChatUsers(chatId, onlyReset) { db - .prepare(`delete from group_users where group_id = :group_id`) - .run({ group_id: groupId }) + .prepare(`delete from chat_users where chat_id = :chat_id`) + .run({ chat_id: chatId }) if (onlyReset) return - const group = db - .prepare(`select telegram_id, is_channel, access_hash from groups where id = :group_id`) - .get({ group_id: groupId}) + const chat = db + .prepare(`select telegram_id, is_channel, access_hash from chats where id = :chat_id`) + .get({ chat_id: chatId}) - console.log (123, group) - - if (!group) + if (!chat) return - const tgGroupId = group.telegram_id - const isChannel = group.is_channel - let accessHash = group.access_hash - - console.log ('HERE') - - db - .prepare(`update groups set access_hash = :access_hash where id = :group_id`) - .safeIntegers(true) - .run({ - group_id: groupId, - access_hash: accessHash, - }) + const tgChatId = chat.telegram_id + const isChannel = chat.is_channel + const accessHash = chat.access_hash const result = isChannel ? await client.invoke(new Api.channels.GetParticipants({ - channel: new Api.PeerChannel({ channelId: tgGroupId }), + channel: new Api.PeerChannel({ channelId: tgChatId, accessHash }), filter: new Api.ChannelParticipantsRecent(), limit: 999999, offset: 0 - })) : await client.invoke(new Api.messages.GetFullChat({ - chatId: tgGroupId, - })) + })) : await client.invoke(new Api.messages.GetFullChat({ chatId: tgChatId, accessHash })) const users = result.users.filter(user => !user.bot) for (const user of users) { @@ -201,37 +200,42 @@ async function reloadGroupUsers(groupId, onlyReset) { if (updateUser(userId, user)) { await updateUserPhoto (userId, user) - const query = `insert or ignore into group_users (group_id, user_id) values (:group_id, :user_id)` - db.prepare(query).run({ group_id: groupId, user_id: userId }) + db + .prepare(`insert or ignore into chat_users (chat_id, user_id) values (:chat_id, :user_id)`) + .run({ chat_id: chatId, user_id: userId }) } } + + db + .prepare(`update chats set user_count = (select count(1) from chat_users where chat_id = :chat_id) where id = :chat_id`) + .run({ chat_id: chatId}) } async function registerUpload(data) { if (!data.projectId || !data.media) - return false + return console.error ('registerUpload: ' + (data.projectId ? 'media' : 'project id') + ' is missing') - const uploadGroup = db - .prepare(` - select id, telegram_id, project_id, is_channel, access_hash - from groups - where id = (select upload_group_id - from customers - where id = (select customer_id from projects where id = :project_id limit 1) - limit 1) - limit 1 - `) - .safeIntegers(true) + const customer_id = db + .prepare(`select customer_id from projects where project_id = :project_id`) + .pluck(true) .get({project_id: data.projectId}) - if (!uploadGroup || !uploadGroup.telegram_id || uploadGroup.id == data.originGroupId) - return false + if (!customer_id) + return console.error ('registerUpload: The customer is not found for project: ' + data.projectId) - const tgUploadGroupId = uploadGroup.telegram_id + const chat = db + .prepare( + `select id, telegram_id, project_id, is_channel, access_hash from chats + where id = (select upload_chat_id from customers where id = :customer_id`) + .safeIntegers(true) + .get({ customer_id }) - const peer = uploadGroup.is_channel ? - new Api.PeerChannel({ channelId: tgUploadGroupId }) : - new Api.PeerChat({ chatlId: tgUploadGroupId }) + if (!chat || !chat.telegram_id || chat.id == data.originchatId) + return console.error ('registerUpload: The upload chat is not set for customer: ' + customer_id) + + const peer = chat.is_channel ? + new Api.PeerChannel({ channelId: chat.telegram_id, accessHash: chat.access_hash }) : + new Api.PeerChat({ chatlId: chat.telegram_id, accessHash: chat.access_hash }) let resultId = 0 @@ -247,26 +251,26 @@ async function registerUpload(data) { const update = result.updates.find(u => (u.className == 'UpdateNewMessage' || u.className == 'UpdateNewChannelMessage') && u.message.className == 'Message' && - (u.message.peerId.channelId?.value == tgUploadGroupId || u.message.peerId.chatId?.value == tgUploadGroupId) && + (u.message.peerId.channelId?.value == chat.telegram_id || u.message.peerId.chatId?.value == chat.telegram_id) && u.message.media) const udoc = update?.message?.media?.document if (udoc) { resultId = db .prepare(` - insert into documents (project_id, origin_group_id, origin_message_id, group_id, message_id, - file_id, access_hash, filename, mime, caption, size, published_by, parent_type, parent_id) - values (:project_id, :origin_group_id, :origin_message_id, :group_id, :message_id, - :file_id, :access_hash, :filename, :mime, :caption, :size, :published_by, :parent_type, :parent_id) + insert into files (project_id, origin_chat_id, origin_message_id, chat_id, message_id, + file_id, access_hash, filename, mime, caption, size, published_by, published, parent_type, parent_id) + values (:project_id, :origin_chat_id, :origin_message_id, :chat_id, :message_id, + :file_id, :access_hash, :filename, :mime, :caption, :size, :published_by, :published, :parent_type, :parent_id) returning id `) .safeIntegers(true) .pluck(true) .get({ project_id: data.projectId, - origin_group_id: data.originGroupId, + origin_chat_id: data.originchatId, origin_message_id: data.originMessageId, - group_id: uploadGroup.id, + chat_id: chat.id, message_id: update.message.id, file_id: udoc.id.value, filename: udoc.attributes.find(attr => attr.className == 'DocumentAttributeFilename')?.fileName, @@ -275,14 +279,15 @@ async function registerUpload(data) { caption: data.caption, size: udoc.size.value, published_by: data.publishedBy, + published: data.published, parent_type: data.parentType, parent_id: data.parentId }) } } catch (err) { - fs.appendFileSync('./1.log', '\n\nERR:' + err.message + ':' + JSON.stringify (err.stack)+'\n\n') - console.error('Message.registerUpload: ' + err.message) + //fs.appendFileSync('./1.log', '\n\nERR:' + err.message + ':' + JSON.stringify (err.stack)+'\n\n') + console.error('registerUpload: ' + err.message) } return resultId @@ -290,14 +295,14 @@ async function registerUpload(data) { async function onNewServiceMessage (msg, isChannel) { const action = msg.action || {} - const tgGroupId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value - const groupId = await registerGroup(tgGroupId, isChannel) + const tgChatId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value + const chatId = await registerChat(tgChatId, isChannel) - // Group/Channel rename + // Сhat rename if (action.className == 'MessageActionChatEditTitle') { const info = db .prepare(` - update groups + update chats set name = :name, is_channel = :is_channel, last_update_time = :last_update_time where telegram_id = :telegram_id `) @@ -306,15 +311,18 @@ async function onNewServiceMessage (msg, isChannel) { name: action.title, is_channel: +isChannel, last_update_time: Math.floor (Date.now() / 1000), - telegram_id: tgGroupId + telegram_id: tgChatId }) + + if (info.changes == 0) + console.error('onNewServiceMessage: Can\'t update a chat title: ' + tgChatId) } // Chat to Channel if (action.className == 'MessageActionChatMigrateTo') { const info = db .prepare(` - update groups + update chats set telegram_id = :new_telegram_id, name = :name, is_channel = 1, last_update_time = :last_update_time where telegram_id = :old_telegram_id `) @@ -322,9 +330,12 @@ async function onNewServiceMessage (msg, isChannel) { .run({ name: action.title, last_update_time: Date.now() / 1000, - old_telegram_id: tgGroupId, + old_telegram_id: tgChatId, new_telegram_id: action.channelId.value }) + + if (info.changes == 0) + console.error('onNewServiceMessage: Can\'t apply a chat migration to channel: ' + tgChatId) } // User/s un/register @@ -335,7 +346,7 @@ async function onNewServiceMessage (msg, isChannel) { const tgUserIds = [action.user, action.users, action.userId].flat().filter(Boolean).map(e => BigInt(e.value)) const isAdd = action.className == 'MessageActionChatAddUser' || action.className == 'MessageActionChannelAddUser' - if (tgUserIds.indexOf(bot.id) == -1) { + if (tgUserIds.indexOf(BOT_ID) == -1) { // Add/remove non-bot users for (const tgUserId of tgUserIds) { const userId = registerUser(tgUserId) @@ -351,27 +362,29 @@ async function onNewServiceMessage (msg, isChannel) { } const query = isAdd ? - `insert or ignore into group_users (group_id, user_id) values (:group_id, :user_id)` : - `delete from group_users where group_id = :group_id and user_id = :user_id` - db.prepare(query).run({ group_id: groupId, user_id: userId }) + `insert or ignore into chat_users (chat_id, user_id) values (:chat_id, :user_id)` : + `delete from chat_users where chat_id = :chat_id and user_id = :user_id` + db + .prepare(query) + .run({ chat_id: chatId, user_id: userId }) } } } } async function onNewMessage (msg, isChannel) { - const tgGroupId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value - const groupId = await registerGroup(tgGroupId, isChannel) + const tgChatId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value + const chatId = await registerChat(tgChatId, isChannel) // Document is detected if (msg.media?.document) { const doc = msg.media.document const projectId = db - .prepare(`select project_id from groups where telegram_id = :telegram_id`) + .prepare(`select project_id from chats where telegram_id = :telegram_id`) .safeIntegers(true) .pluck(true) - .get({telegram_id: tgGroupId}) + .get({telegram_id: tgChatId}) const media = new Api.InputMediaDocument({ id: new Api.InputDocument({ @@ -385,101 +398,55 @@ async function onNewMessage (msg, isChannel) { projectId, media, caption: msg.message, - originGroupId: groupId, + originchatId: chatId, originMessageId: msg.id, parentType: 0, - publishedBy: registerUser (msg.fromId?.userId?.value) + publishedBy: registerUser (msg.fromId?.userId?.value), + published: msg.date }) } - if (msg.message?.startsWith('KEY-')) { - let projectName = db + if (msg.message?.startsWith(`/start@${BOT_NAME} KEY-`) || msg.message?.startsWith('KEY-')) { + const rows = db .prepare(` - select name - from projects - where id in ( - select project_id from groups where id = :group_id - union - select id from projects where upload_group_id = :group_id) + select 1 from chats where id = :chat_id and project_id is not null + union all + select 1 from customers where upload_chat_id = :chat_id `) - .pluck(true) - .get({ group_id: groupId }) + .all({ chat_id: chatId }) - if (projectName) - return await bot.sendMessage(groupId, 'Группа уже используется на проекте ' + projectName) + if (rows.length) + return await sendMessage(chatId, 'Чат уже используется') - const [_, time64, key] = msg.message.substr(3).split('-') + const rawkey = msg.message.substr(msg.message?.indexOf('KEY-')) + const [_, time64, key] = rawkey.split('-') const now = Math.floor(Date.now() / 1000) const time = Buffer.from(time64, 'base64') if (now - 3600 >= time && time >= now) - return await bot.sendMessage(groupId, 'Время действия ключа для привязки истекло') + return await sendMessage(chatId, 'Время действия ключа для привязки истекло') - const projectId = db - .prepare(`select id from projects where generate_key(id, :time) = :key`) - .pluck(true) - .get({ key: msg.message.trim(), time }) - - if (projectId) { - await attachGroup(groupId, isChannel, projectId) - await onGroupAttach(tgGroupId, isChannel) + const row = db + .prepare(` + select (select id from projects where generate_key(id, :time) = :rawkey) project_id, + (select id from customers where generate_key(-id, :time) = :rawkey) customer_id + `) + .get({ rawkey, time }) + + if (row.project_id) { + await attachChat(chatId, row.project_id) + await reloadChatUsers(chatId) } - } - - if (msg.message?.startsWith('/start')) { - // Called by https://t.me/ready_or_not_2025_bot?startgroup= - if (/start@ready_or_not_2025_bot (-|)([\d]+)$/g.test(msg.message)) { - const tgUserId = msg.fromId?.userId?.value - const param = +msg.message.split(' ')[1] - // Set upload group for customer - if (param < 0) { - const customerId = -param + if (row.customer_id) { + const info = db + .prepare(`update customers set upload_chat_id = :chat_id where id = :customer_id`) + .safeIntegers(true) + .run({ customer_id: row.customer_id, chat_id: chatId }) - db - .prepare(` - update customers - set upload_group_id = :group_id - where id = :customer_id and telegram_user_id = :telegram_user_id - `) - .safeIntegers(true) - .run({ - group_id: groupId, - customer_id: customerId, - telegram_user_id: tgUserId - }) - } - - // Add group to project - if (param > 0) { - const projectId = param - - const customerId = db - .prepare(`select customer_id from projects where id = :project_id`) - .pluck(true) - .get({project_id: projectId}) - - db - .prepare(` - update groups - set project_id = :project_id - where id = :group_id and exists( - select 1 - from customers - where id = :customer_id and telegram_user_id = :telegram_user_id) - `) - .safeIntegers(true) - .run({ - project_id: projectId, - group_id: groupId, - customer_id: customerId, - telegram_user_id: tgUserId - }) - - await reloadGroupUsers(groupId, false) - await onGroupAttach(tgGroupId, isChannel) - } - } + if (info.changes == 0) + console.error('Can\'t set upload chat: ' + chatId + ' to customer: ' + row.customer_id) + } } } @@ -493,12 +460,19 @@ async function onNewUserMessage (msg) { updateUser(userId, user) await updateUserPhoto (userId, user) + const appButton = new Api.KeyboardButtonWebView({ + text: "Open Mini-App", // Текст на кнопке + url: "https://h5sj0gpz-3000.euw.devtunnels.ms/", // URL вашего Mini-App (HTTPS!) + }); + + const inputPeer = new Api.InputPeerUser({userId: tgUserId, accessHash: user.accessHash.value}) - const resultBtn = await client.sendMessage(inputPeer, { + await client.sendMessage(inputPeer, { message: 'Сообщение от бота', buttons: client.buildReplyMarkup([ - [Button.url('Админка', 'https://t.me/ready_or_not_2025_bot/userapp?startapp=admin')], - [Button.url('Пользователь', 'https://t.me/ready_or_not_2025_bot/userapp?startapp=user')] + [Button.url('Админка', `https://t.me/${BOT_NAME}/userapp?startapp=admin`)], + [Button.url('Пользователь', `https://t.me/${BOT_NAME}/userapp?startapp=user`)], + [appButton] ]) }) } catch (err) { @@ -508,41 +482,125 @@ async function onNewUserMessage (msg) { } async function onUpdatePaticipant (update, isChannel) { - const tgGroupId = isChannel ? update.channelId?.value : update.chatlId?.value - if (!tgGroupId || update.userId?.value != bot.id) + const tgChatId = isChannel ? update.channelId?.value : update.chatlId?.value + if (!tgChatId || update.userId?.value != BOT_ID) return - const groupId = await registerGroup (tgGroupId, isChannel) + const chatId = await registerChat (tgChatId, isChannel) const isBan = update.prevParticipant && !update.newParticipant const isAdd = (!update.prevParticipant || update.prevParticipant?.className == 'ChannelParticipantBanned') && update.newParticipant if (isBan || isAdd) - await reloadGroupUsers(groupId, isBan) + await reloadChatUsers(chatId, isBan) if (isBan) { db - .prepare(`update groups set project_id = null where id = :group_id`) - .run({group_id: groupId}) + .prepare(`update chats set project_id = null where id = :chat_id`) + .run({chat_id: chatId}) } const botCanBan = update.newParticipant?.adminRights?.banUsers || 0 db - .prepare(`update groups set bot_can_ban = :bot_can_ban where id = :group_id`) - .run({group_id: groupId, bot_can_ban: +botCanBan}) + .prepare(`update chats set bot_can_ban = :bot_can_ban where id = :chat_id`) + .run({chat_id: chatId, bot_can_ban: +botCanBan}) } -class Bot extends EventEmitter { - - async start (apiId, apiHash, botAuthToken) { - this.id = 7236504417n +async function uploadFile(projectId, fileName, mime, data, parentType, parentId, publishedBy) { + const file = await client.uploadFile({ file: new CustomFile(fileName, data.length, '', data), workers: 1 }) - client = new TelegramClient(session, apiId, apiHash, {}) + const media = new Api.InputMediaUploadedDocument({ + file, + mimeType: mime, + attributes: [new Api.DocumentAttributeFilename({ fileName })] + }) - client.addEventHandler(async (update) => { - if (update.className == 'UpdateConnectionState') - return + return await registerUpload({ + projectId, + media, + parentType, + parentId, + publishedBy, + published: Math.floor(Date.now() / 1000) + }) +} +async function downloadFile(projectId, fileId) { + const file = db + .prepare(` + select file_id, access_hash, '' thumbSize, filename, mime + from files where id = :id and project_id = :project_id + `) + .safeIntegers(true) + .get({project_id: projectId, id: fileId}) + + if (!file) + return false + + const result = await client.downloadFile(new Api.InputDocumentFileLocation({ + id: file.file_id, + accessHash: file.access_hash, + fileReference: Buffer.from(file.filename), + thumbSize: '' + }, {})) + + return { + filename: file.filename, + mime: file.mime, + size: result.length, + data: result + } +} + +async function sendMessage (chatId, message) { + const chat = db + .prepare(`select telegram_id, is_channel from chats where id = :chat_id`) + .get({ chat_id: chatId}) + + if (!chat) + return + + const entity = chat.is_channel ? { channelId: chat.telegram_id } : { chatId: chat.telegram_id } + const inputPeer = await client.getEntity( chat.is_channel ? + new Api.InputPeerChannel(entity) : + new Api.InputPeerChat(entity) + ) + + await client.sendMessage(inputPeer, {message}) + + const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) + await delay(1000) +} + +async function leaveChat (chatId) { + const chat = db + .prepare(`select telegram_id, access_hash, is_channel from chats where id = :chat_id`) + .get({ chat_id: chatId}) + + if (!chat) + return + + if (chat.is_channel) { + const inputPeer = await client.getEntity(new Api.InputPeerChannel({ channelId: chat.telegram_id, accessHash: chat.access_hash })) + await client.invoke(new Api.channels.LeaveChannel({ channel: inputPeer })) + } else { + await client.invoke(new Api.messages.DeleteChatUser({ chatId: chat.telegram_id, userId: this.id, accessHash: chat.access_hash })) + } +} + +async function start (apiId, apiHash, botAuthToken, sid) { + BOT_ID = BigInt(botAuthToken.split(':')[0]) + + session= new StringSession(sid || '') + client = new TelegramClient(session, apiId, apiHash, {}) + + client.addEventHandler(async (update) => { + if (update.className == 'UpdateConnectionState') + return + + try { + // console.log(update) + if (update.className == 'UpdateNewMessage' || update.className == 'UpdateNewChannelMessage') { const msg = update?.message const isChannel = update.className == 'UpdateNewChannelMessage' @@ -557,98 +615,13 @@ class Bot extends EventEmitter { if (update.className == 'UpdateChatParticipant' || update.className == 'UpdateChannelParticipant') await onUpdatePaticipant(update, update.className == 'UpdateChannelParticipant') - }) - - await client.start({botAuthToken}) - } - - async uploadDocument(projectId, fileName, mime, data, parentType, parentId, publishedBy) { - const file = await client.uploadFile({ file: new CustomFile(fileName, data.length, '', data), workers: 1 }) - - const media = new Api.InputMediaUploadedDocument({ - file, - mimeType: mime, - attributes: [new Api.DocumentAttributeFilename({ fileName })] - }) - - return await registerUpload({ - projectId, - media, - parentType, - parentId, - publishedBy - }) - } - - async downloadDocument(projectId, documentId) { - const document = db - .prepare(` - select file_id, access_hash, '' thumbSize, filename, mime - from documents where id = :document_id and project_id = :project_id - `) - .safeIntegers(true) - .get({project_id: projectId, document_id: documentId}) - - if (!document) - return false - - const result = await client.downloadFile(new Api.InputDocumentFileLocation({ - id: document.file_id, - accessHash: document.access_hash, - fileReference: Buffer.from(document.filename), - thumbSize: '' - }, {})) - - return { - filename: document.filename, - mime: document.mime, - size: result.length, - data: result + } catch (err) { + console.error(err) } - } + }) - async reloadGroupUsers(groupId, onlyReset) { - return reloadGroupUsers(groupId, onlyReset) - } + await client.start({botAuthToken}) +} - async sendMessage (groupId, message) { - const group = db - .prepare(`select telegram_id, is_channel from groups where id = :group_id`) - .get({ group_id: groupId}) - - if (!group) - return - - const entity = group.is_channel ? { channelId: group.telegram_id } : { chatId: group.telegram_id } - const inputPeer = await client.getEntity( group.is_channel ? - new Api.InputPeerChannel(entity) : - new Api.InputPeerChat(entity) - ) - - await client.sendMessage(inputPeer, {message}) - - const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) - await delay(1000) - } - - async leaveGroup (groupId) { - const group = db - .prepare(`select telegram_id, is_channel from groups where id = :group_id`) - .get({ group_id: groupId}) - - if (!group) - return - - if (group.is_channel) { - const inputPeer = await client.getEntity(new Api.InputPeerChannel({ channelId: group.telegram_id })) - await client.invoke(new Api.channels.LeaveChannel({ channel: inputPeer })) - } else { - await client.invoke(new Api.messages.DeleteChatUser({ chatId: group.telegram_id, userId: this.id })) - } - } -} - -const bot = new Bot() - -module.exports = bot +module.exports = { start, uploadFile, downloadFile, reloadChatUsers, sendMessage } diff --git a/backend/data/init.sql b/backend/data/init.sql index 675cc28..2fa537c 100644 --- a/backend/data/init.sql +++ b/backend/data/init.sql @@ -5,15 +5,15 @@ create table if not exists customers ( name text check(name is null or trim(name) <> '' and length(name) < 256), email text check(email is null or trim(email) <> '' and length(email) < 128), password text check(password is null or length(password) > 7 and length(password) < 64), - telegram_user_id integer, + telegram_id integer, plan integer, json_balance text default '{}', - activation_key text, - is_active integer default 0, + is_blocked integer default 0, json_company text default '{}', - upload_group_id integer, + upload_chat_id integer, json_backup_server text default '{}', - json_backup_params text default '{}' + json_backup_params text default '{}', + json_settings text default '{}' ) strict; create table if not exists projects ( @@ -22,34 +22,36 @@ create table if not exists projects ( name text not null check(trim(name) <> '' and length(name) < 256), description text check(description is null or length(description) < 4096), logo text, - is_deleted integer default 0 + is_logo_bg integer default 0, + is_archived integer default 0 ) strict; -create table if not exists groups ( +create table if not exists chats ( id integer primary key autoincrement, project_id integer references projects(id) on delete cascade, name text, telegram_id integer, + description text, + logo text, access_hash integer, is_channel integer check(is_channel in (0, 1)) default 0, bot_can_ban integer default 0, + owner_id integer references users(id) on delete set null, user_count integer, last_update_time integer ); -create unique index if not exists idx_groups_telegram_id on groups (telegram_id); +create unique index if not exists idx_chats_telegram_id on chats (telegram_id); create table if not exists users ( id integer primary key autoincrement, telegram_id integer, access_hash integer, - firstname text, - lastname text, - username text, + firstname text check(firstname is null or length(firstname) < 256), + lastname text check(lastname is null or length(lastname) < 256), + username text check(username is null or length(username) < 256), photo_id integer, photo text, language_code text, - phone text, - json_phone_projects text default '[]', json_settings text default '{}' ) strict; create unique index if not exists idx_users_telegram_id on users (telegram_id); @@ -57,9 +59,11 @@ create unique index if not exists idx_users_telegram_id on users (telegram_id); create table if not exists user_details ( user_id integer references users(id) on delete cascade, project_id integer references projects(id) on delete cascade, - fullname text, - role text, - department text, + fullname text check(fullname is null or length(fullname) < 256), + email text check(email is null or length(email) < 256), + phone text check(phone is null or length(phone) < 256), + role text check(role is null or length(role) < 256), + department text check(department is null or length(department) < 256), is_blocked integer check(is_blocked in (0, 1)) default 0, primary key (user_id, project_id) ) strict; @@ -88,12 +92,12 @@ create table if not exists meetings ( meet_date integer ) strict; -create table if not exists documents ( +create table if not exists files ( id integer primary key autoincrement, project_id integer references projects(id) on delete cascade, - origin_group_id integer references groups(id) on delete set null, + origin_chat_id integer references chats(id) on delete set null, origin_message_id integer, - group_id integer references groups(id) on delete set null, + chat_id integer references chats(id) on delete set null, message_id integer, file_id integer, access_hash integer, @@ -102,6 +106,7 @@ create table if not exists documents ( caption text check(caption is null or length(caption) < 4096), size integer, published_by integer references users(id) on delete set null, + published integer, parent_type integer check(parent_type in (0, 1, 2)) default 0, parent_id integer, backup_state integer default 0 @@ -111,6 +116,7 @@ create table if not exists companies ( id integer primary key autoincrement, project_id integer references projects(id) on delete cascade, name text not null check(length(name) < 4096), + address text check(address is null or length(address) < 512), email text check(email is null or length(email) < 128), phone text check(phone is null or length(phone) < 128), site text check(site is null or length(site) < 128), @@ -143,20 +149,19 @@ create table if not exists company_users ( primary key (company_id, user_id) ) without rowid; -create table if not exists group_users ( - group_id integer references groups(id) on delete cascade, +create table if not exists chat_users ( + chat_id integer references chats(id) on delete cascade, user_id integer references users(id) on delete cascade, - primary key (group_id, user_id) + primary key (chat_id, user_id) ) without rowid; pragma foreign_keys = on; -create trigger if not exists trg_groups_update after update -on groups +create trigger if not exists trg_chats_update after update on chats when NEW.project_id is null begin - delete from group_users where group_id = NEW.id; + delete from chat_users where chat_id = NEW.id; end; diff --git a/i18n-2.xlsm b/i18n-2.xlsm index 246ef95fefd8a88ce2c2e9c8ed94f5d6faeadd1d..2f472860926c07abd1c9e3177d9635637e5ecfae 100644 GIT binary patch delta 31511 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 diff --git a/icons-svg/_old/icon-3-points.svg b/icons-svg/_old/icon-3-points.svg new file mode 100644 index 0000000..45061b0 --- /dev/null +++ b/icons-svg/_old/icon-3-points.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-bell-solid.svg b/icons-svg/_old/icon-bell-solid.svg new file mode 100644 index 0000000..b035fcb --- /dev/null +++ b/icons-svg/_old/icon-bell-solid.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-bell.svg b/icons-svg/_old/icon-bell.svg new file mode 100644 index 0000000..2c16e72 --- /dev/null +++ b/icons-svg/_old/icon-bell.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-bookmark.svg b/icons-svg/_old/icon-bookmark.svg new file mode 100644 index 0000000..75a4988 --- /dev/null +++ b/icons-svg/_old/icon-bookmark.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-calendar.svg b/icons-svg/_old/icon-calendar.svg new file mode 100644 index 0000000..8d9689c --- /dev/null +++ b/icons-svg/_old/icon-calendar.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-change.svg b/icons-svg/_old/icon-change.svg new file mode 100644 index 0000000..a360c36 --- /dev/null +++ b/icons-svg/_old/icon-change.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-clear.svg b/icons-svg/_old/icon-clear.svg new file mode 100644 index 0000000..bffa7da --- /dev/null +++ b/icons-svg/_old/icon-clear.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-clipboard.svg b/icons-svg/_old/icon-clipboard.svg new file mode 100644 index 0000000..f9284d3 --- /dev/null +++ b/icons-svg/_old/icon-clipboard.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-download.svg b/icons-svg/_old/icon-download.svg new file mode 100644 index 0000000..923d2ca --- /dev/null +++ b/icons-svg/_old/icon-download.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-dropdown.svg b/icons-svg/_old/icon-dropdown.svg new file mode 100644 index 0000000..ebe51d1 --- /dev/null +++ b/icons-svg/_old/icon-dropdown.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-exit.svg b/icons-svg/_old/icon-exit.svg new file mode 100644 index 0000000..0a258e6 --- /dev/null +++ b/icons-svg/_old/icon-exit.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-eye.svg b/icons-svg/_old/icon-eye.svg new file mode 100644 index 0000000..aab114d --- /dev/null +++ b/icons-svg/_old/icon-eye.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-add.svg b/icons-svg/_old/icon-file-add.svg new file mode 100644 index 0000000..2ed67fb --- /dev/null +++ b/icons-svg/_old/icon-file-add.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-archive.svg b/icons-svg/_old/icon-file-archive.svg new file mode 100644 index 0000000..7ad2930 --- /dev/null +++ b/icons-svg/_old/icon-file-archive.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-audio.svg b/icons-svg/_old/icon-file-audio.svg new file mode 100644 index 0000000..d0a2c91 --- /dev/null +++ b/icons-svg/_old/icon-file-audio.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-delete.svg b/icons-svg/_old/icon-file-delete.svg new file mode 100644 index 0000000..90da6c1 --- /dev/null +++ b/icons-svg/_old/icon-file-delete.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-doc.svg b/icons-svg/_old/icon-file-doc.svg new file mode 100644 index 0000000..110f42d --- /dev/null +++ b/icons-svg/_old/icon-file-doc.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-dwg.svg b/icons-svg/_old/icon-file-dwg.svg new file mode 100644 index 0000000..5ff1137 --- /dev/null +++ b/icons-svg/_old/icon-file-dwg.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-img.svg b/icons-svg/_old/icon-file-img.svg new file mode 100644 index 0000000..fbc4d12 --- /dev/null +++ b/icons-svg/_old/icon-file-img.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-lock.svg b/icons-svg/_old/icon-file-lock.svg new file mode 100644 index 0000000..8f54bf1 --- /dev/null +++ b/icons-svg/_old/icon-file-lock.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-merge.svg b/icons-svg/_old/icon-file-merge.svg new file mode 100644 index 0000000..3d1094f --- /dev/null +++ b/icons-svg/_old/icon-file-merge.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-move.svg b/icons-svg/_old/icon-file-move.svg new file mode 100644 index 0000000..a93ea32 --- /dev/null +++ b/icons-svg/_old/icon-file-move.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-move.svg.2019_10_02_17_03_29.0.svg b/icons-svg/_old/icon-file-move.svg.2019_10_02_17_03_29.0.svg new file mode 100644 index 0000000..1ba8485 --- /dev/null +++ b/icons-svg/_old/icon-file-move.svg.2019_10_02_17_03_29.0.svg @@ -0,0 +1,64 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons-svg/_old/icon-file-pdf.svg b/icons-svg/_old/icon-file-pdf.svg new file mode 100644 index 0000000..e916a14 --- /dev/null +++ b/icons-svg/_old/icon-file-pdf.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-ppt.svg b/icons-svg/_old/icon-file-ppt.svg new file mode 100644 index 0000000..bded736 --- /dev/null +++ b/icons-svg/_old/icon-file-ppt.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-rename.svg b/icons-svg/_old/icon-file-rename.svg new file mode 100644 index 0000000..c92510d --- /dev/null +++ b/icons-svg/_old/icon-file-rename.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-skp.svg b/icons-svg/_old/icon-file-skp.svg new file mode 100644 index 0000000..0125894 --- /dev/null +++ b/icons-svg/_old/icon-file-skp.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-ttf.svg b/icons-svg/_old/icon-file-ttf.svg new file mode 100644 index 0000000..413d6fa --- /dev/null +++ b/icons-svg/_old/icon-file-ttf.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-txt.svg b/icons-svg/_old/icon-file-txt.svg new file mode 100644 index 0000000..3e79ffe --- /dev/null +++ b/icons-svg/_old/icon-file-txt.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-types.svg b/icons-svg/_old/icon-file-types.svg new file mode 100644 index 0000000..f4ed68f --- /dev/null +++ b/icons-svg/_old/icon-file-types.svg @@ -0,0 +1,202 @@ + + + + + + + + + + image/svg+xml + + + + + + + + X + + + P + + W + + + V + + + + + + + + + diff --git a/icons-svg/_old/icon-file-update.svg b/icons-svg/_old/icon-file-update.svg new file mode 100644 index 0000000..071a909 --- /dev/null +++ b/icons-svg/_old/icon-file-update.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-video.svg b/icons-svg/_old/icon-file-video.svg new file mode 100644 index 0000000..021cb24 --- /dev/null +++ b/icons-svg/_old/icon-file-video.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-vsd.svg b/icons-svg/_old/icon-file-vsd.svg new file mode 100644 index 0000000..4a4b8e9 --- /dev/null +++ b/icons-svg/_old/icon-file-vsd.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file-xls.svg b/icons-svg/_old/icon-file-xls.svg new file mode 100644 index 0000000..9a2f4b2 --- /dev/null +++ b/icons-svg/_old/icon-file-xls.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-file.svg b/icons-svg/_old/icon-file.svg new file mode 100644 index 0000000..d080be2 --- /dev/null +++ b/icons-svg/_old/icon-file.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-filter.svg b/icons-svg/_old/icon-filter.svg new file mode 100644 index 0000000..e9beebd --- /dev/null +++ b/icons-svg/_old/icon-filter.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-flag-solid.svg b/icons-svg/_old/icon-flag-solid.svg new file mode 100644 index 0000000..0355b8b --- /dev/null +++ b/icons-svg/_old/icon-flag-solid.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-flag.svg b/icons-svg/_old/icon-flag.svg new file mode 100644 index 0000000..ca49463 --- /dev/null +++ b/icons-svg/_old/icon-flag.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-folder-delete.svg b/icons-svg/_old/icon-folder-delete.svg new file mode 100644 index 0000000..ef734a3 --- /dev/null +++ b/icons-svg/_old/icon-folder-delete.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-folder-move.svg b/icons-svg/_old/icon-folder-move.svg new file mode 100644 index 0000000..0cfc391 --- /dev/null +++ b/icons-svg/_old/icon-folder-move.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-folder-over.svg b/icons-svg/_old/icon-folder-over.svg new file mode 100644 index 0000000..036d0a7 --- /dev/null +++ b/icons-svg/_old/icon-folder-over.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons-svg/_old/icon-folder-plus.svg b/icons-svg/_old/icon-folder-plus.svg new file mode 100644 index 0000000..f962982 --- /dev/null +++ b/icons-svg/_old/icon-folder-plus.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-folder-rename.svg b/icons-svg/_old/icon-folder-rename.svg new file mode 100644 index 0000000..4ea6c71 --- /dev/null +++ b/icons-svg/_old/icon-folder-rename.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-folder-update.svg b/icons-svg/_old/icon-folder-update.svg new file mode 100644 index 0000000..366918f --- /dev/null +++ b/icons-svg/_old/icon-folder-update.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-folder.svg b/icons-svg/_old/icon-folder.svg new file mode 100644 index 0000000..a04805a --- /dev/null +++ b/icons-svg/_old/icon-folder.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-full-view.svg b/icons-svg/_old/icon-full-view.svg new file mode 100644 index 0000000..57c2062 --- /dev/null +++ b/icons-svg/_old/icon-full-view.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-gear.svg b/icons-svg/_old/icon-gear.svg new file mode 100644 index 0000000..1f3607d --- /dev/null +++ b/icons-svg/_old/icon-gear.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-info.svg b/icons-svg/_old/icon-info.svg new file mode 100644 index 0000000..19e5427 --- /dev/null +++ b/icons-svg/_old/icon-info.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-left-arrow.svg b/icons-svg/_old/icon-left-arrow.svg new file mode 100644 index 0000000..d525c28 --- /dev/null +++ b/icons-svg/_old/icon-left-arrow.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-lock-close.svg b/icons-svg/_old/icon-lock-close.svg new file mode 100644 index 0000000..8de196a --- /dev/null +++ b/icons-svg/_old/icon-lock-close.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-lock-open.svg b/icons-svg/_old/icon-lock-open.svg new file mode 100644 index 0000000..a5b1535 --- /dev/null +++ b/icons-svg/_old/icon-lock-open.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-logo-2.svg.png b/icons-svg/_old/icon-logo-2.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..8d67e75730751ad00f1538480962985adcec4ded GIT binary patch literal 4584 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 literal 0 HcmV?d00001 diff --git a/icons-svg/_old/icon-logo.png b/icons-svg/_old/icon-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c0349ee6437b7b41499cd4a49eee7f94f98cf5f2 GIT binary patch literal 6087 zcmcIoc|4Tu*T2WuXON_^?-9a8WEsNP6`E{WN3s+#Whc{&VU$V|Nkt}9mY9?!O9oF- zmPT17#f&^kVkm3Y_a4vh`MmEx@8|vNozL8#Gxs^?dwtJ+u6xdPuIq}6ldX`Tlpp{A zLiTo67yy8nb1r^fP}7@LS`G^CC`)@+e(-aV|MX?>UI1rzG71206>}~~f6e?GP$?O0 z?HPR{GB`Re;6e}(7Z;~}CL$~LA#QH!oF zje9c>xs7SJnFWR%=BiyH=27X^J~xh6Dvd2%n^%h z75o3=TVlBg^EH1cMLb4Oqy-bhj-%WGXR@~CpdMgSuZ|Ru5Vm~;(H<>ZPQAL;3wj*}smbiV9gT4~zd`etE4?moS!+p(9tA=wu zC=leEgEU=B-zp~FlrWzjBhqk&k*ZjJ4RI4Vd2UO#8O2u$Q<>o+vt)8#Ktr{|AU(=C%{?wk zwpOZ-Tbz_HpB5CQ%utY-lx`)5U)HnWp?*@u{mIX&wV^5+zB9oS_1Ywa_2n&k4f0fRXFeCp=rb8|B;v!T|i5GH$aM86G|5P+~Aa=*P_qQA;GZWE$lL zLjXBbsCGV72#xkoib3v`IGHb?edZuHsiGd(_;$eXr{&a~mUf2Q=c71q$k2kO4vWDP zfLxN&#@{~1t&iO^tUn)}$A+W?3_1dGiQGM}cCAU`9jw=n(gRb*2he~Mia(-(i?`u( z#Xx1;QTp=^NQoT5dxk&m3;*@Pd6#mOiHy@b2qSX8&w1sGq3QHoyW{~gv1U2H$h39z z@QJ1lR6VXdAc`PlQAril0l zw*vhAT}o;UcQ;7%ePY<25mzdcWQ8`Ywg%)NP!&gqOjK9TYNB>E7A9tYfaBJ6+94#g zhawN%Kn%sq$G*zWheQZ&8%i(FO;3Ksn1(f31&>4~6D%Vz0yKPjTd6;=;9UA`pY9)r zAVmDv$JlPVwcSC(jCWT={a=2Hv*%_Z?-*!Bi(;&V`LqcOQmj>aZOMTv019~2G^g3} zF;UOL`Pw2z5Mp@RdDVPMsYF6nxQF~)sBO$%5m>pK)%RAa?Au6e`uhk%pyhD(-922m z;d5vY1?#GFajO?YQcQ1I0_>R|{$)u&d$~;yVoIl=IAG@M1iN%P{!~fGLlK%H^{I$y zZh8IK713(38Jk5&gFyJ+MqYLObBghWdTmwK@&q)^6kCR9wtAG*&&N`j|8)tYlW?je zte+1Hg$w$Ua&+fp**=SqsSfATR||JoO;^o;79Oj2>7Cq%_apS!bpSX8;}dcP(e3&PAe2fVzD6{BCbb zKVOlWMahW934R-SLLX2%qUp#lMaUj=le7fg2g(vf+NPlNKmx`y9r}Iv2imPzgf<1s zalrB}{i%C7web3IyZ!kmoudIz7tG-Fxi|(0Qr08D#_uQcgB+L}y+vl0$Tc8m&tu6F zc@CYPxw@a8oe8&)xqY%ODVh`2K!~259!K%=jzywkDB$p}7ydzz1Fs6sa^l2u;V{%t zx=+B@HV$rXpJuo}8sdfo_T}D&>@Rx(YD$9u2MsOI|BNId+o2nLPA}e~k`EbXz`nBm zJDm2`2mhzPcrxICIsPy5=@b+R^Cgt{*#~^SzJvJ>`otX@M~4%Okp2I_-N969Q%fFN z$rG|&bHKbv7X9aD&%fN7Yrvw!zuFuk1vcf>(g!?9A4(s8oiIO-i4QeGF$E)q)2>U6 zoAx5h#clN48)gFrY>NUu(ydoM=(7Am{eXqrZDK!JzwlHlEv>^e$Mj6A#=EuS=dc(1T2)GsunsQQ&K z6vbrAlLuO>M2|!f7%kQUpiEbwM9kxGQT~@{qW8l6DIIyM;li?oZ7ZOp!L7!plX> z$t+pe4XF2C#@rB-{CL=!9vMl7+#yBU&RZ`>H$y)4&p)X3vYlWprbCxa5H&61CbiRX zhwdl()9?tifn?Pq12xgZcLnNO#M|Z?A9nyf7H1(15;d*qmnxPLrC^Uu#fq5U6{JO& zl`*YNZDmo=nsI5kS6I;B*G>4y8mG7ZDujaxdTc#!?QYUXLhwI7|3Z3I`Hp`D{s!?S zWxaqWDr|5rj-`)%PS`Z?6YjR$?yHdH#R`KZl8+LkG9!_g+#(;FK;<3o^M&-%mMQBz zZ+xJ=)+_74X>KK{{= zCiVG`-%?tc@7rT2>)~<68)l1hEq4QeZwhHZbG@$g2x_tM>@ffJDA;}R0-lO1PyDe9 z%(jt-Yb`upgq8G&n$bFH$HrwHc%=M$m0bigxFTO(TQ2PbJ?2GN%A>IHA*I`+Ts~Y! zlpo@WrEzV?thF|D8+hVNpG>X{z>tx`Ul5JLgS2|3RmY*hcxcT{q`_k1^wb2Ba=BYI zqH4o!?rn0F{xkWR+ByQ>pBx^@(22ezdyGW7IyS4*0Y@f2<74U12T#Qe^N2$3V?Cafe`1U>TZ4%;dpoYVG{U0ZpO2-vj3WY(pc<=H~M=#WA}>$jGkBvxH> zUVa`=Zuf-?OD;hkdT$;CEW1v+qn0cdAreik?Lw5Wsh*1sn(rs|AuF`&3EFzjnyfZd zULGXQgZ(}8L;E}HPt#B+Rm2@y-)2hrIo0#~epr%lDDm-vPjI1o$AjNKV->WCdGo^V zo{xICkSQq)A{ltoFxSsMiQR{iZ_MobA|z7#t)baNQ(ouqhAtl=@?#LQ@vgHOm6!kc z`}j?|eu?%PaP3pgyUq-%TQnQ*x#*SI?d|sGavgJatgrIoAvd$z-(U8iHX(a$)`L`( zK<5NrhR+{QL|uk<5YNT7{^fOz%ATl%3QhU2-#)>8Lu74*7;rJ-I)(S>r=uP?88`nH zJ=ZGIzBfTTvdhHm>qmZcv6i+d=+dJ{t3&uTtA&gEN*&zanRf03ht>mIfyz5PBP;(a zU%2>Gv-!b&99J7Il2Wtg!_h;GI9z7;XHkaaa@YBrFVCh5BxqO9_L~)RQu2moiyo%u z-ci$;1=rF$9$#%}rwx(OvIUZ`TMIsZm$14=d!zCbMh<-kWQ4EXV2OGbeBx3iS5Y+8 zdz`>kig7H}Ikk$S=Z&EL!teh|W7ZJxD&Ph`a&P{&9G7K38b7}a7n(hbRDFZ&u^5mJN<$zCyM$jIM8Hl0e znml)BdR1|8GIpqh>lJgB!t@wVO3W3h%ZHL5Ah7KkTQog2&h zDN^(KLsYf#kEhqG36s_-k#n3td?$UgFSXk{j2M zsoUj5I7t~AJYX1nMri#Uc}B)i-?jDQ_!EyX#@r~qS^pYra8BjUYB-k;`m8*sJ_*v` zH3{?!o&?QBXTptESJDb3PUaYWO=tdx*Gdv_G_iqq4$3wR_V6Pj9}aCsaC!T=k$T0(ak9 ztPIHNbQ#QJ?;XBg+l~P1>Iaduo-{}>Ubb_0=%!iEfkx(!`T9#j$c3MVc%H}^cyR`M zrG+|?MpcP!ZIJ}wnKwZAY`knubw@bX98wX?x-niZF1q;1>^3?!c2f3j+M)>zzkG^2 zx$ef1_7QY|4WxyOY9fPoBSl*Mc(}?!3&u}h&KmXR7|0G?c9vxY5 z{WJDQ^qoSF)+E{s*m$i3lk}PUlL)J@U<_~UrCqlsxMB8)I=7SaNigTAH&?R?;5QDt zXXXmcyeqzS$@ZF%78r9;kD z=l1V>##>`Vo%Y6crjK`zE;SvwA20ajym+Obw8f^WEKI?Kuh69ALhEA0=tlE8r0H1t z-1e@*gaavS3o_vA2=}Oq`(fCHmukG6(hNrlqJ*A0VqXKECcA0^kf*ufaVJ%X^zv(q+}U?Z^eW)9#RyqY5@` ziWo*L*mS1Z{oD0{wMp=jwaU5uY3GJHJtWvUmK-WYCR{NCPIz9)HppqO3;R!_$-x$j znA@Mx1){S4UkCi(SV}oPtiuVxTX|p&OUvDV2CW|6^l#T&s-E`NadGESUr_ec`TnOp z6};JAC%pr6oZ>3ql?*(Z_ptgv23sfz0_N^rG{S|4lb3(`;%_v`A}~)#K3-5br`Ty| zg%FHzy!%Vv0zE{?e~Um)%Pol&z7KKWWCIIu#J=323_6di;u;VQBF?yhbf7^a6>4izw;33^r%qHJ(P-x0}wdlVqI;sF?Cwgsl3-n{h}UX#o%5-qtJNn$ydo{Nq%{ zoZvXMfwQd-4H{>H=jJ3znDEPDagA;Jz93qP6;69Ri&n*5sGC=akNcZ zJ3Yo#IT!d_aP_Siay|)00K!F>7`#Gva z1JD<_-g&wNr)FpCoeVF0DQ;l@EWS)GS=7CcEE~UWfjlFztC8RrA7<8E3%htKe9A%P zyxP3Jp?bweAO=%lwZs(Ox^U#0ylCU@vcKFRVyym7EDGE{6c#$nN?@CYc#sSHp-vIk z9b^7ZqHjpZSf`Bhondp;dmD<3dGX0VkO6V2Ra{QxGi!cgjkm`fi~tCGUHRIk!c1Kl zc&@TLf!mO~%7@1v=IiKRk_g8o-#gs?Ad+05np$P0LEJ}EiT|d~fO*O_7QV7ul3=af zHfZZK6VO5J1_$obhddy@c5Wl8$l+b&A^h{de~+Jr1WcJgy~ z*8z<)N~^1`@uQF5*!}T>%kVpHamyM(bA^qsdaBo=UWmF2NZU`+( zOEfB}EB4aV!Rj4SovKU-qJ(K!n)>j5EcnsXp%aXrSOrgO1Xf`6lyLus88b$%u}G`#$R+3awXE_T(C zLx?2H$P$eqb5_@8z}T$l#;Q!F3%E9fSQ-cxa+xvf&gD2rWs(ugKWy^iTx>oRm;5=Cpe#bvk>~fGg@wz451}TRn~vy#RFM;I zbm$QMkEiF=F6KZQD859f8SpVS)jqK3ya)CWRW=tJ&hJ&Y$i7P~rAgC#G$Pe5@!yq8 zH9@q`JwZMD)MPyz2*gIJAbA-Aj50`_6i-JzNsDYTv5sXWVOcl{20zpw9fA&^1tY1m zQ8X6`WP?n|7f8Hd&EaX5O#&g8-dJ~av?I7{EM=(b_Om^An?_oF1fGT&Zydf`w2xFN z={dAa?FYRYXSavBwY0LdNJFae3&%X`Dzh)a%6euBBcyfQ2A{Z=l!?kLR=sWz@~ThO zA^S12l`v#r!}LXFYB9Px%>HKC3UFvcC8oR(N}szu{NAx$_`PGxUX$X)a(ZbWa4BTf-r-~Y}1GVR4foM>W-)i})M znuEL?E!$O$gJ~S_6GbXV l%VzIs51_!Z7M-^Fp1&27Z8CCE0T0~)duu1FDobq2e*wkc=b8Wj literal 0 HcmV?d00001 diff --git a/icons-svg/_old/icon-logo.svg b/icons-svg/_old/icon-logo.svg new file mode 100644 index 0000000..63a3358 --- /dev/null +++ b/icons-svg/_old/icon-logo.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-mail.svg b/icons-svg/_old/icon-mail.svg new file mode 100644 index 0000000..3523091 --- /dev/null +++ b/icons-svg/_old/icon-mail.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-messege-2.svg b/icons-svg/_old/icon-messege-2.svg new file mode 100644 index 0000000..90a63fa --- /dev/null +++ b/icons-svg/_old/icon-messege-2.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons-svg/_old/icon-messege-2.svg.png b/icons-svg/_old/icon-messege-2.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..6c92ada37d6cb3b91cec8af4590fe57ca2f49cdc GIT binary patch literal 405 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8wRq z_>O=u<5X=vX`rBFiEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$QVa}?LY^*; zArY-_r=9IOWFXRN|D3VMU{~?|O2zGuxqH6u6e+p0>qD!Hz}(*bFBYwgdF&f~G|kMi zVBsC-qJ1j5ye@ZNbI)Lme(>qpEvEerK_dIsDc)GSl|M;L!Df9YTfp&czq2N{o3R{l zUA!ugx1s;QLjEoW!2|3Y*n<|ZN+e`d@8r8?zAfl>@p13?zHrt3m*tQ8x6a@9fPYSp z6T{*kxevFC?pOZff7D#+fbshSo;LEL@!!scs()pynf~RMYTeC>(4y+Hl=N#Vw|6zZ z-MMe&19Q>;Wh@`#1sIhYSblKNsNOxvv%%G&Vdpg;j$++-##s4CCZChrR`kvOutMX` xRO^F6FG6Mi@SXf%c=Ns4ihrqBgB-(ZS@UK-{x7$}4;X?B44$rjF6*2UngCckqwW9z literal 0 HcmV?d00001 diff --git a/icons-svg/_old/icon-messege-3.svg.png b/icons-svg/_old/icon-messege-3.svg.png new file mode 100644 index 0000000000000000000000000000000000000000..da0edb60bb224f4e0a6a34926f2d8e61f7dd5a17 GIT binary patch literal 90674 zcmeFa2~ZT*_dj~s1Vur=M%-9jzQ*_y#g%0c&=@6&0xq*D+bB^)R)GN-wlPLYf46z#9|XoFqM{H(ntf9gq(M-zK?Z?$nh4Xr`oF4IZ>f4!j;p6+|k`JB&L z?(OFEFWc;Gr%akN2_ZD)yKleQfzV_j^`F)RIHL2@`NQy!dcfD;F}2_mruDqp_Qb7Yt2`9CrKbNlQKr?*W$Kk3nb9vENQ@W(^P z%0D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 Zt?r)G8ur6(GZ|^|s+D#tgv(g_{})DZCgcDB literal 0 HcmV?d00001 diff --git a/icons-svg/_old/icon-messege-done.svg b/icons-svg/_old/icon-messege-done.svg new file mode 100644 index 0000000..c11672a --- /dev/null +++ b/icons-svg/_old/icon-messege-done.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-messege-second.svg b/icons-svg/_old/icon-messege-second.svg new file mode 100644 index 0000000..76e7b72 --- /dev/null +++ b/icons-svg/_old/icon-messege-second.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-messege.svg b/icons-svg/_old/icon-messege.svg new file mode 100644 index 0000000..debcca4 --- /dev/null +++ b/icons-svg/_old/icon-messege.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-open-folder.svg b/icons-svg/_old/icon-open-folder.svg new file mode 100644 index 0000000..9cb65a9 --- /dev/null +++ b/icons-svg/_old/icon-open-folder.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-paperclip.svg b/icons-svg/_old/icon-paperclip.svg new file mode 100644 index 0000000..3ee20ef --- /dev/null +++ b/icons-svg/_old/icon-paperclip.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-pencil.svg b/icons-svg/_old/icon-pencil.svg new file mode 100644 index 0000000..aad5bfc --- /dev/null +++ b/icons-svg/_old/icon-pencil.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-plus.svg b/icons-svg/_old/icon-plus.svg new file mode 100644 index 0000000..2660c08 --- /dev/null +++ b/icons-svg/_old/icon-plus.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-question.svg b/icons-svg/_old/icon-question.svg new file mode 100644 index 0000000..683e884 --- /dev/null +++ b/icons-svg/_old/icon-question.svg @@ -0,0 +1,40 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/icons-svg/_old/icon-question2.svg b/icons-svg/_old/icon-question2.svg new file mode 100644 index 0000000..475beb3 --- /dev/null +++ b/icons-svg/_old/icon-question2.svg @@ -0,0 +1,70 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/icons-svg/_old/icon-right-arrow.svg b/icons-svg/_old/icon-right-arrow.svg new file mode 100644 index 0000000..bb147f9 --- /dev/null +++ b/icons-svg/_old/icon-right-arrow.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-save (2).svg b/icons-svg/_old/icon-save (2).svg new file mode 100644 index 0000000000000000000000000000000000000000..6a755b6c7904b0d0215e66a3e4e52acefdd69fa2 GIT binary patch literal 3440 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∨ + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-search-1.svg b/icons-svg/_old/icon-search-1.svg new file mode 100644 index 0000000..e8a82ac --- /dev/null +++ b/icons-svg/_old/icon-search-1.svg @@ -0,0 +1,146 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/icons-svg/_old/icon-search-file.svg b/icons-svg/_old/icon-search-file.svg new file mode 100644 index 0000000..c7c9ea7 --- /dev/null +++ b/icons-svg/_old/icon-search-file.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-search-task.svg b/icons-svg/_old/icon-search-task.svg new file mode 100644 index 0000000..6ad3fb1 --- /dev/null +++ b/icons-svg/_old/icon-search-task.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-send.svg b/icons-svg/_old/icon-send.svg new file mode 100644 index 0000000..10fa45a --- /dev/null +++ b/icons-svg/_old/icon-send.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-simple-view.svg b/icons-svg/_old/icon-simple-view.svg new file mode 100644 index 0000000..5d316fb --- /dev/null +++ b/icons-svg/_old/icon-simple-view.svg @@ -0,0 +1,32 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-sort.svg b/icons-svg/_old/icon-sort.svg new file mode 100644 index 0000000..0da7f36 --- /dev/null +++ b/icons-svg/_old/icon-sort.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-support.svg b/icons-svg/_old/icon-support.svg new file mode 100644 index 0000000..0bf3ed4 --- /dev/null +++ b/icons-svg/_old/icon-support.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-task-cancel.svg b/icons-svg/_old/icon-task-cancel.svg new file mode 100644 index 0000000..aba5ccc --- /dev/null +++ b/icons-svg/_old/icon-task-cancel.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-task-comment.svg b/icons-svg/_old/icon-task-comment.svg new file mode 100644 index 0000000..a275843 --- /dev/null +++ b/icons-svg/_old/icon-task-comment.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-task-delete.svg b/icons-svg/_old/icon-task-delete.svg new file mode 100644 index 0000000..4ccd728 --- /dev/null +++ b/icons-svg/_old/icon-task-delete.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-task-done.svg b/icons-svg/_old/icon-task-done.svg new file mode 100644 index 0000000..807d6f9 --- /dev/null +++ b/icons-svg/_old/icon-task-done.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-task-plus.svg b/icons-svg/_old/icon-task-plus.svg new file mode 100644 index 0000000..7e8b714 --- /dev/null +++ b/icons-svg/_old/icon-task-plus.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-task-remark.svg b/icons-svg/_old/icon-task-remark.svg new file mode 100644 index 0000000..1a41d26 --- /dev/null +++ b/icons-svg/_old/icon-task-remark.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-task-request.svg b/icons-svg/_old/icon-task-request.svg new file mode 100644 index 0000000..7773212 --- /dev/null +++ b/icons-svg/_old/icon-task-request.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-telephone.svg b/icons-svg/_old/icon-telephone.svg new file mode 100644 index 0000000..ad144de --- /dev/null +++ b/icons-svg/_old/icon-telephone.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-time-load.svg b/icons-svg/_old/icon-time-load.svg new file mode 100644 index 0000000..3f62b9d --- /dev/null +++ b/icons-svg/_old/icon-time-load.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-timer.svg b/icons-svg/_old/icon-timer.svg new file mode 100644 index 0000000..9c92d51 --- /dev/null +++ b/icons-svg/_old/icon-timer.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/_old/icon-upload.svg b/icons-svg/_old/icon-upload.svg new file mode 100644 index 0000000..b6c7d77 --- /dev/null +++ b/icons-svg/_old/icon-upload.svg @@ -0,0 +1,44 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons-svg/_old/icon-user-solid.svg b/icons-svg/_old/icon-user-solid.svg new file mode 100644 index 0000000..5949225 --- /dev/null +++ b/icons-svg/_old/icon-user-solid.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-user.svg b/icons-svg/_old/icon-user.svg new file mode 100644 index 0000000..f09a3ed --- /dev/null +++ b/icons-svg/_old/icon-user.svg @@ -0,0 +1,31 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/_old/icon-users-solid.svg b/icons-svg/_old/icon-users-solid.svg new file mode 100644 index 0000000..4581f6e --- /dev/null +++ b/icons-svg/_old/icon-users-solid.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-archive.svg b/icons-svg/icon-file-archive.svg new file mode 100644 index 0000000..7ad2930 --- /dev/null +++ b/icons-svg/icon-file-archive.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-audio.svg b/icons-svg/icon-file-audio.svg new file mode 100644 index 0000000..d0a2c91 --- /dev/null +++ b/icons-svg/icon-file-audio.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-code.svg b/icons-svg/icon-file-code.svg new file mode 100644 index 0000000..caec668 --- /dev/null +++ b/icons-svg/icon-file-code.svg @@ -0,0 +1,34 @@ + + + + + + + image/svg+xml + + + + + + + diff --git a/icons-svg/icon-file-default.svg b/icons-svg/icon-file-default.svg new file mode 100644 index 0000000..d080be2 --- /dev/null +++ b/icons-svg/icon-file-default.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-doc.svg b/icons-svg/icon-file-doc.svg new file mode 100644 index 0000000..110f42d --- /dev/null +++ b/icons-svg/icon-file-doc.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-dwg.svg b/icons-svg/icon-file-dwg.svg new file mode 100644 index 0000000..5ff1137 --- /dev/null +++ b/icons-svg/icon-file-dwg.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-img.svg b/icons-svg/icon-file-img.svg new file mode 100644 index 0000000..fbc4d12 --- /dev/null +++ b/icons-svg/icon-file-img.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-pdf.svg b/icons-svg/icon-file-pdf.svg new file mode 100644 index 0000000..e916a14 --- /dev/null +++ b/icons-svg/icon-file-pdf.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-ppt.svg b/icons-svg/icon-file-ppt.svg new file mode 100644 index 0000000..bded736 --- /dev/null +++ b/icons-svg/icon-file-ppt.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-skp.svg b/icons-svg/icon-file-skp.svg new file mode 100644 index 0000000..0125894 --- /dev/null +++ b/icons-svg/icon-file-skp.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-ttf.svg b/icons-svg/icon-file-ttf.svg new file mode 100644 index 0000000..413d6fa --- /dev/null +++ b/icons-svg/icon-file-ttf.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-txt.svg b/icons-svg/icon-file-txt.svg new file mode 100644 index 0000000..3e79ffe --- /dev/null +++ b/icons-svg/icon-file-txt.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-video.svg b/icons-svg/icon-file-video.svg new file mode 100644 index 0000000..021cb24 --- /dev/null +++ b/icons-svg/icon-file-video.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-vsd.svg b/icons-svg/icon-file-vsd.svg new file mode 100644 index 0000000..4a4b8e9 --- /dev/null +++ b/icons-svg/icon-file-vsd.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/icons-svg/icon-file-xls.svg b/icons-svg/icon-file-xls.svg new file mode 100644 index 0000000..9a2f4b2 --- /dev/null +++ b/icons-svg/icon-file-xls.svg @@ -0,0 +1,36 @@ + + + + + + + image/svg+xml + + + + + + + + diff --git a/package-lock.json b/package-lock.json index cbf24a2..0545aad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@quasar/cli": "^2.5.0", "@quasar/extras": "^1.16.4", "axios": "^1.2.1", + "dayjs": "^1.11.13", "pinia": "^2.0.11", "quasar": "^2.16.0", "vue": "^3.4.18", @@ -1661,9 +1662,9 @@ } }, "node_modules/@quasar/extras": { - "version": "1.16.16", - "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.16.16.tgz", - "integrity": "sha512-aswGUbEyLvt45KB1u6hBD3s82KnOdkqTn6YVu3xX5aGgwQkCWPyqb3FMTEHG+4+gGTMp4pIcnng96RlqswQctQ==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.17.0.tgz", + "integrity": "sha512-KqAHdSJfIDauiR1nJ8rqHWT0diqD0QradZKoVIZJAilHAvgwyPIY7MbyR2z4RIMkUIMUSqBZcbshMpEw+9A30w==", "license": "MIT", "funding": { "type": "github", @@ -4593,6 +4594,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -9049,9 +9056,9 @@ } }, "node_modules/quasar": { - "version": "2.17.7", - "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.17.7.tgz", - "integrity": "sha512-nPJdHoONlcW7WEU2Ody907Wx945Zfyuea/KP4LBaEn5AcL95PUWp8Gz/0zDYNnFw0aCWRtye3SUAdQl5tmrn5w==", + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.18.1.tgz", + "integrity": "sha512-db/P64Mzpt1uXJ0MapaG+IYJQ9hHDb5KtTCoszwC78DR7sA+Uoj7nBW2EytwYykIExEmqavOvKrdasTvqhkgEg==", "license": "MIT", "engines": { "node": ">= 10.18.1", diff --git a/package.json b/package.json index c3c4c86..48a5004 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@quasar/cli": "^2.5.0", "@quasar/extras": "^1.16.4", "axios": "^1.2.1", + "dayjs": "^1.11.13", "pinia": "^2.0.11", "quasar": "^2.16.0", "vue": "^3.4.18", diff --git a/quasar.config.ts b/quasar.config.ts index cfae256..902d21b 100644 --- a/quasar.config.ts +++ b/quasar.config.ts @@ -3,6 +3,7 @@ import { defineConfig } from '#q-app/wrappers' import { fileURLToPath } from 'node:url' +import path from 'path' export default defineConfig((ctx) => { return { @@ -14,10 +15,9 @@ export default defineConfig((ctx) => { // https://v2.quasar.dev/quasar-cli-vite/boot-files boot: [ 'i18n', + 'telegram-boot', 'axios', - 'auth-init', - 'global-components', - 'telegram-boot' + 'global-components' ], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#css @@ -41,11 +41,18 @@ export default defineConfig((ctx) => { // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build build: { + publicPath: '/', + vueRouterBase: '/', target: { browser: [ 'es2022', 'firefox115', 'chrome115', 'safari14' ], node: 'node20' }, + alias: { + 'composables': path.resolve(__dirname, './src/composables'), + 'types': path.resolve(__dirname, './src/types') + }, + typescript: { strict: true, vueShim: true @@ -53,8 +60,8 @@ export default defineConfig((ctx) => { }, vueRouterMode: 'history', // available values: 'hash', 'history' - vueDevtools: true, // Должно быть true - devtool: 'source-map', // Для лучшей отладки + // vueDevtools: true, // Должно быть true + // devtool: 'source-map', // Для лучшей отладки // vueRouterBase, // vueDevtools, // vueOptionsAPI: false, @@ -103,8 +110,8 @@ export default defineConfig((ctx) => { // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#devserver devServer: { - vueDevtools: true, - port: 9000, + // vueDevtools: true, + port: 9005, proxy: { '/api': { target: 'http://localhost:3000', @@ -132,7 +139,7 @@ export default defineConfig((ctx) => { // directives: [], // Quasar plugins - plugins: [ 'Notify' ] + plugins: [ 'Notify', 'BottomSheet' ] }, // animations: 'all', // --- includes all animations diff --git a/src/App.vue b/src/App.vue index 2df1ee8..d2eeb01 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,10 +3,64 @@ + + + + \ No newline at end of file diff --git a/src/boot/auth-init.ts b/src/boot/auth-init.ts deleted file mode 100644 index 8ec1dd9..0000000 --- a/src/boot/auth-init.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { useAuthStore } from 'stores/auth' - -export default async () => { - const authStore = useAuthStore() - await authStore.initialize() -} \ No newline at end of file diff --git a/src/boot/axios.ts b/src/boot/axios.ts index 2272de7..4c44fdc 100644 --- a/src/boot/axios.ts +++ b/src/boot/axios.ts @@ -16,23 +16,10 @@ declare module 'vue' { // "export default () => {}" function below (which runs individually // for each client) const api = axios.create({ - baseURL: '/', + baseURL: '/api/miniapp', withCredentials: true // Важно для работы с cookies }) -api.interceptors.response.use( - response => response, - async error => { - if (error.response?.status === 401) { - const authStore = useAuthStore() - await authStore.logout() - } - console.error(error) - return Promise.reject(new Error()) - } - -) - export default defineBoot(({ app }) => { // for use inside Vue files (Options API) through this.$axios and this.$api @@ -43,6 +30,6 @@ export default defineBoot(({ app }) => { app.config.globalProperties.$api = api // ^ ^ ^ this will allow you to use this.$api (for Vue Options API form) // so you can easily perform requests against your app's API -}); +}) -export { api }; +export { api } diff --git a/src/boot/global-components.ts b/src/boot/global-components.ts index d9cba81..4488a41 100644 --- a/src/boot/global-components.ts +++ b/src/boot/global-components.ts @@ -1,14 +1,24 @@ import { boot } from 'quasar/wrappers' -import pnPageCard from '../components/admin/pnPageCard.vue' -import pnScrollList from '../components/admin/pnScrollList.vue' -import pnAutoAvatar from '../components/admin/pnAutoAvatar.vue' -import pnOverlay from '../components/admin/pnOverlay.vue' -import pnImageSelector from '../components/admin/pnImageSelector.vue' +import pnPageCard from 'components/pnPageCard.vue' +import pnScrollList from 'components/pnScrollList.vue' +import pnAutoAvatar from 'components/pnAutoAvatar.vue' +import pnOverlay from 'components/pnOverlay.vue' +import pnDialogBody from 'components/pnDialogBody.vue' +import pnImageSelector from 'components/pnImageSelector.vue' +import pnTaskPriorityIcon from 'components/pnTaskPriorityIcon.vue' -export default boot(async ({ app }) => { // eslint-disable-line - app.component('pnPageCard', pnPageCard) - app.component('pnScrollList', pnScrollList) - app.component('pnAutoAvatar', pnAutoAvatar) - app.component('pnOverlay', pnOverlay) - app.component('pnImageSelector', pnImageSelector) +const components = { + pnPageCard, + pnScrollList, + pnAutoAvatar, + pnOverlay, + pnImageSelector, + pnDialogBody, + pnTaskPriorityIcon +} + +export default boot(({ app }) => { + Object.entries(components).forEach(([name, component]) => { + app.component(name, component) + }) }) diff --git a/src/boot/helpers.ts b/src/boot/helpers.ts index 286e65a..6b1a2ea 100644 --- a/src/boot/helpers.ts +++ b/src/boot/helpers.ts @@ -1,38 +1,73 @@ -export function isObjEqual(a: object, b: object): boolean { - // Сравнение примитивов и null/undefined - if (a === b) return true - if (!a || !b) return false - if (Object.keys(a).length !== Object.keys(b).length) return false +function isDirty ( + obj1: Record | null | undefined, + obj2: Record | null | undefined +): boolean { + const actualObj1 = obj1 ?? {} + const actualObj2 = obj2 ?? {} - // Получаем все уникальные ключи из обоих объектов - const allKeys = new Set([ - ...Object.keys(a), - ...Object.keys(b) - ]) + const filteredObj1 = filterIgnored(actualObj1) + const filteredObj2 = filterIgnored(actualObj2) + + const allKeys = new Set([...Object.keys(filteredObj1), ...Object.keys(filteredObj2)]) - // Проверяем каждое свойство for (const key of allKeys) { - const valA = a[key as keyof typeof a] - const valB = b[key as keyof typeof b] - - // Если одно из значений undefined - объекты разные - if (valA === undefined || valB === undefined) return false - - // Рекурсивное сравнение для вложенных объектов - if (typeof valA === 'object' && typeof valB === 'object') { - if (!isObjEqual(valA, valB)) return false - } - // Сравнение примитивов - else if (!Object.is(valA, valB)) { - return false - } + const hasKey1 = Object.hasOwn(filteredObj1, key) + const hasKey2 = Object.hasOwn(filteredObj2, key) + + if (hasKey1 !== hasKey2) return false + + if (hasKey1 && hasKey2) { + const val1 = filteredObj1[key] + const val2 = filteredObj2[key] + + if (typeof val1 === 'string' && typeof val2 === 'string') { + if (val1.trim() !== val2.trim()) return false + } else if (val1 !== val2) { + return false + } + } } return true } -export function parseIntString (s: string | string[] | undefined) :number | null { +function filterIgnored(obj: Record): Record { + const filtered: Record = {} + + for (const key in obj) { + const originalValue = obj[key] + + // Пропускаем значения, которые не string, number или boolean + if ( + typeof originalValue !== 'string' && + typeof originalValue !== 'number' && + typeof originalValue !== 'boolean' + ) { + continue + } + + let value = originalValue + + if (typeof value === 'string') { + value = value.trim() + if (value === '') continue + } + + if (value === 0 || value === false) continue + + filtered[key] = value + } + + return filtered +} + +function parseIntString (s: string | string[] | undefined) :number | null { if (typeof s !== 'string') return null const regex = /^[+-]?\d+$/ return regex.test(s) ? Number(s) : null +} + +export { + isDirty, + parseIntString } \ No newline at end of file diff --git a/src/boot/i18n.ts b/src/boot/i18n.ts index 5b89b30..8514993 100644 --- a/src/boot/i18n.ts +++ b/src/boot/i18n.ts @@ -1,11 +1,11 @@ -import { defineBoot } from '#q-app/wrappers'; -import { createI18n } from 'vue-i18n'; +import { defineBoot } from '#q-app/wrappers' +import { createI18n } from 'vue-i18n' -import messages from 'src/i18n'; +import messages from 'src/i18n' -export type MessageLanguages = keyof typeof messages; +export type MessageLanguages = keyof typeof messages // Type-define 'en-US' as the master schema for the resource -export type MessageSchema = typeof messages['en-US']; +export type MessageSchema = typeof messages['en-US'] // See https://vue-i18n.intlify.dev/guide/advanced/typescript.html#global-resource-schema-type-definition /* eslint-disable @typescript-eslint/no-empty-object-type */ @@ -25,9 +25,9 @@ export default defineBoot(({ app }) => { const i18n = createI18n<{ message: MessageSchema }, MessageLanguages>({ locale: 'en-US', legacy: false, - messages, - }); + messages + }) // Set i18n instance on app - app.use(i18n); -}); + app.use(i18n) +}) diff --git a/src/boot/telegram-boot.ts b/src/boot/telegram-boot.ts index 960a4ea..aa8c7df 100644 --- a/src/boot/telegram-boot.ts +++ b/src/boot/telegram-boot.ts @@ -1,18 +1,25 @@ -import type { BootParams } from '@quasar/app' +import { defineBoot } from '#q-app/wrappers' +import type { WebApp } from "@twa-dev/types" +import { useProjectsStore } from 'stores/projects' -export default ({ app }: BootParams) => { - - // Инициализация Telegram WebApp +declare global { + interface Window { + Telegram: { + WebApp: WebApp + } + } +} + +export default defineBoot(({ app }) => { if (window.Telegram?.WebApp) { const webApp = window.Telegram.WebApp - // Помечаем приложение как готовое webApp.ready() - // window.Telegram.WebApp.requestFullscreen() - // Опционально: сохраняем объект в Vue-приложение для глобального доступа webApp.SettingsButton.isVisible = true - // webApp.BackButton.isVisible = true app.config.globalProperties.$tg = webApp - // Для TypeScript: объявляем тип для инжекции + const projectStore = useProjectsStore() + if (Number(webApp.initDataUnsafe.start_param)) { + projectStore.setStartProjectId(Number(webApp.initDataUnsafe.start_param)) + } app.provide('tg', webApp) } -} \ No newline at end of file +}) diff --git a/src/components/admin/account-page/optionPayment.vue b/src/components/admin/account-page/optionPayment.vue deleted file mode 100644 index b909710..0000000 --- a/src/components/admin/account-page/optionPayment.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - - - diff --git a/src/components/admin/account-page/qtyChatCard.vue b/src/components/admin/account-page/qtyChatCard.vue deleted file mode 100644 index 9ff7891..0000000 --- a/src/components/admin/account-page/qtyChatCard.vue +++ /dev/null @@ -1,47 +0,0 @@ - - - - - diff --git a/src/components/admin/accountHelper.vue b/src/components/admin/accountHelper.vue deleted file mode 100644 index 5bde9b2..0000000 --- a/src/components/admin/accountHelper.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/components/admin/companyInfoBlock.vue b/src/components/admin/companyInfoBlock.vue deleted file mode 100644 index ebb4812..0000000 --- a/src/components/admin/companyInfoBlock.vue +++ /dev/null @@ -1,80 +0,0 @@ - - - - - diff --git a/src/components/admin/login-page/loginLogo.vue b/src/components/admin/login-page/loginLogo.vue deleted file mode 100644 index 8403946..0000000 --- a/src/components/admin/login-page/loginLogo.vue +++ /dev/null @@ -1,118 +0,0 @@ - - - - - diff --git a/src/components/admin/pnPageCard.vue b/src/components/admin/pnPageCard.vue deleted file mode 100644 index b9c056e..0000000 --- a/src/components/admin/pnPageCard.vue +++ /dev/null @@ -1,23 +0,0 @@ - - - - - diff --git a/src/components/admin/pnScrollList.vue b/src/components/admin/pnScrollList.vue deleted file mode 100644 index 03470a0..0000000 --- a/src/components/admin/pnScrollList.vue +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/components/admin/project-page/ProjectPageChats.vue b/src/components/admin/project-page/ProjectPageChats.vue deleted file mode 100644 index 181e3d2..0000000 --- a/src/components/admin/project-page/ProjectPageChats.vue +++ /dev/null @@ -1,256 +0,0 @@ - - - - - diff --git a/src/components/admin/project-page/ProjectPageCompanies.vue b/src/components/admin/project-page/ProjectPageCompanies.vue deleted file mode 100644 index e254b3c..0000000 --- a/src/components/admin/project-page/ProjectPageCompanies.vue +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - diff --git a/src/components/admin/project-page/ProjectPageHeader.vue b/src/components/admin/project-page/ProjectPageHeader.vue deleted file mode 100644 index 8422be1..0000000 --- a/src/components/admin/project-page/ProjectPageHeader.vue +++ /dev/null @@ -1,202 +0,0 @@ - - - - - diff --git a/src/components/admin/project-page/ProjectPagePersons.vue b/src/components/admin/project-page/ProjectPagePersons.vue deleted file mode 100644 index f29da04..0000000 --- a/src/components/admin/project-page/ProjectPagePersons.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - - - diff --git a/src/components/admin/companyInfoPersons.vue b/src/components/companyInfoPersons.vue similarity index 100% rename from src/components/admin/companyInfoPersons.vue rename to src/components/companyInfoPersons.vue diff --git a/src/components/meetingBlock.vue b/src/components/meetingBlock.vue new file mode 100644 index 0000000..4a2cdbd --- /dev/null +++ b/src/components/meetingBlock.vue @@ -0,0 +1,265 @@ + + + + + diff --git a/src/components/admin/meshBackground.vue b/src/components/meshBackground.vue similarity index 100% rename from src/components/admin/meshBackground.vue rename to src/components/meshBackground.vue diff --git a/src/components/admin/pnAutoAvatar.vue b/src/components/pnAutoAvatar.vue similarity index 88% rename from src/components/admin/pnAutoAvatar.vue rename to src/components/pnAutoAvatar.vue index d68a2cb..fb6c8fa 100644 --- a/src/components/admin/pnAutoAvatar.vue +++ b/src/components/pnAutoAvatar.vue @@ -3,7 +3,7 @@ :style="{ backgroundColor: stringToColour(props.name) } " class="fit flex items-center justify-center text-white" > - {{ props.name.substring(0, 1) }} + {{ props.name ? props.name.substring(0, 1) : '' }} @@ -14,6 +14,7 @@ }>() const stringToColour = (str: string) => { + if (!str) return '#eee' let hash = 0 str.split('').forEach(char => { hash = char.charCodeAt(0) + ((hash << 5) - hash) diff --git a/src/components/pnDialogBody.vue b/src/components/pnDialogBody.vue new file mode 100644 index 0000000..da26e2c --- /dev/null +++ b/src/components/pnDialogBody.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/components/admin/pnImageSelector.vue b/src/components/pnImageSelector.vue similarity index 100% rename from src/components/admin/pnImageSelector.vue rename to src/components/pnImageSelector.vue diff --git a/src/components/admin/pnOverlay.vue b/src/components/pnOverlay.vue similarity index 100% rename from src/components/admin/pnOverlay.vue rename to src/components/pnOverlay.vue diff --git a/src/components/pnPageCard.vue b/src/components/pnPageCard.vue new file mode 100644 index 0000000..db8cf53 --- /dev/null +++ b/src/components/pnPageCard.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/src/components/pnScrollList.vue b/src/components/pnScrollList.vue new file mode 100644 index 0000000..0d88983 --- /dev/null +++ b/src/components/pnScrollList.vue @@ -0,0 +1,180 @@ + + + + + \ No newline at end of file diff --git a/src/components/pnTaskPriorityIcon.vue b/src/components/pnTaskPriorityIcon.vue new file mode 100644 index 0000000..298b338 --- /dev/null +++ b/src/components/pnTaskPriorityIcon.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/components/admin/projectInfoBlock.vue b/src/components/projectInfoBlock.vue similarity index 100% rename from src/components/admin/projectInfoBlock.vue rename to src/components/projectInfoBlock.vue diff --git a/src/components/taskItem.vue b/src/components/taskItem.vue new file mode 100644 index 0000000..4ff1afc --- /dev/null +++ b/src/components/taskItem.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/css/app.scss b/src/css/app.scss index 77bcac9..c07df57 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -34,13 +34,90 @@ $base-height: 100; body { overflow: hidden !important; } + +.main-content { + max-width: 600px; + margin: 0 auto; +} + +.fix-fab-offset { + margin-right: calc(max((100vw - var(--body-width))/2, 0px) + 18px) !important; +} .projects-header { background-color: #eee; } .top-rounded-card { - border-top-left-radius: var(--top-raduis); - border-top-right-radius: var(--top-raduis); + border-top-left-radius: var(--top-raduis) !important; + border-top-right-radius: var(--top-raduis) !important; +} + +.orline { + display: flex; + flex-direction: row; +} + +.orline:before, +.orline:after { + content: ""; + flex: 1 1; + border-bottom: 1px solid; + margin: auto; +} + +.pn-icon { + font-family: 'pn-icon'; + font-style: normal; +} + +@font-face { + font-family: 'pn-icon'; + src: url("./fonts/pn.woff") format("woff") +} + +.icon-file-default:before { + content: "\e900"; +} +.icon-file-doc:before { + content: "\e901"; +} +.icon-file-xls:before { + content: "\e902"; +} +.icon-file-ppt:before { + content: "\e903"; +} +.icon-file-vsd:before { + content: "\e904"; +} +.icon-file-pdf:before { + content: "\e905"; +} +.icon-file-archive:before { + content: "\e906"; +} +.icon-file-img:before { + content: "\e907"; +} +.icon-file-dwg:before { + content: "\e908"; +} +.icon-file-skp:before { + content: "\e909"; +} +.icon-file-ttf:before { + content: "\e90a"; +} +.icon-file-txt:before { + content: "\e90b"; +} +.icon-file-audio:before { + content: "\e90c"; +} +.icon-file-video:before { + content: "\e90d"; +} +.icon-file-code:before { + content: "\e90e"; } - \ No newline at end of file diff --git a/src/css/fonts/pn.eot b/src/css/fonts/pn.eot new file mode 100644 index 0000000000000000000000000000000000000000..5ac9bdd7c2665528ca562917d101dc1bf0ff2b54 GIT binary patch literal 4740 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 SvDY2=>PsFxiK3 + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/css/fonts/pn.ttf b/src/css/fonts/pn.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1078b5d5eed90eb01831260d260bf1b94be865f3 GIT binary patch literal 4596 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 L@g|Cv{@3(xZL6E> literal 0 HcmV?d00001 diff --git a/src/css/fonts/pn.woff b/src/css/fonts/pn.woff new file mode 100644 index 0000000000000000000000000000000000000000..432f8d6f645fc0f0d9380afb7300f44c9452f535 GIT binary patch literal 4672 zcmbVQYm8gP6`nKC`>?Nleebo`kImY9)QQWyRGTobk{u-y9ogUxeGccQnK&( z^5PouPGS4AI%kgj_8TV`&uQLOZ0oT)Cr_Ta_z?2mB*ZD|Jn`=5_nul>JV8j}8um}? zJo3zI&z?di4liJPr_Smr_SUK8^N%5~f$ckVp2yTx~pt4I#(oc&4uwdKXfP;c_b z*e*u701vJ#E-xYPE!2Hj=YzFhezdlF?mVuyJw}gZIGVBd;A=z>LSLl6!uIp+8vaab zU}C#FL9)B0=>{S1JFL0UBn^fcIe>XH6(b_jf>Z(fG+n?_k17yO_F!$Hrzrk_6yJ9? zu5WB0k04DV8aDoz;T?&n0}CL4qY`=+)J#w_C1hqcQvE;iSQBOAgm{B8aQ$?xw0pYl z!g{`3&NGrPZ{XDB{J)|$ZAQ~;O&cf`$>p|7idr7@!$KRvu4%4cUpsvNgZCd=yZFd` z_dU`(>4&W+;LAE+|2-_XzOL7ZCud7Xmh_VnDU)4fj8s3VFEdkPURY6~SFQ!&3}&^h z?lzE|?sR4c;1l>#IWPi2=moXi(=+>^J~dPshl-ekSqN&u9;o^BE7EjvcjNWNHsat#C>zM34 zBhgN?5HczVgRou;YV`_M-OhgF(&JBD`uPR;=)ly%!a^gw^mwmHAGVg@CEWCb2c}*w zE)*|?MO2G1aYOG*@yy>UvWbu;2k@7I#})Uk|))iNk5wGp++8(XW`=CoD6 zGg-C0=i}YM^#?9lEXjqL;vy3dW=p1$+-BhygCNfsWx#o2|7~W9D$a+Ke!)$qEt{Lt zvsK)6ld;05LLrrRfB&u-980~aAdyX4mTj3T;c`ZWwRa+LoW#f^2WEJ?3zcnh2ayLsI+SMpL z+d;ZjYP_8q!m7(ia4O;q%6i##_ z%>_ugBF5x|D?MfSQaap8VEAFcVMcgy4WrL6Jt;D_l{M10OXeu$@Klb1Z5T<$l1jPU z&oPK8H;o~!e<;jZR6}1f5gJBH@STA_ol1>5P5=g#oW`U`NjZ|~8^PGe$yB_WGd*ES zfLRI&GnMxgffi|(3sC(BU^#YHT8hP#k&?pD5N_$2D7THWk7q=fTyV*`67fD;B&AF#X~RDOp*@z2 z?4o=|Ab?xaHPCF|_L=O#w*c>P+cBntE}~O+!x@3bdSe|LQPm4A%|>e-5OH+biz@+s zC)`DjZPx=eydj1vgea=|0pl|W5hylq19iM`?Sq>;_p&y~1YyKPeod&F7?cSuwiWsH z?%Qqa#L-tDF8#3dFnw}x%J+Pw7V({v8qeoP-IN;y>05@!lvKVG!;_~LEYr%T+~IWV z*ew@fW#Nz7JNv=y-pROlWeO2W@b-i7S;u=#HNufhwJvlY z-i$hZ`p#49^tt7(?OK5H0D=J>I)IU9m3LxrdX;4BHDu#~9Xg=H%`Ar+FZ`h-e6M$r z@3o%w$C7xyUFzXk^Xs}a40yNJ`pu0F2D*dcyBy%ce9zgsne5-bDm8~7t?@XWsYW+! z4Y+ay^etOMD6VxcZg>QjGd1Oob;ISGXv_TU+`ZWxWV1)+=H~HcJcrFJWc!ZH&fN+> zg?C!Ry^CmO`#y(VG8&jeu~{vKadZNN?BXo)sWiG$X*X@>k=9hxJ3xI+t) zCT@q8$e-xY7MUWa)>g#k{lsRFnb9r4z+t7mJ6Euim-3x-w1e{TtqTpKSmD literal 0 HcmV?d00001 diff --git a/src/i18n/en-US/index.ts b/src/i18n/en-US/index.ts index 40f68b3..ada2b0e 100644 --- a/src/i18n/en-US/index.ts +++ b/src/i18n/en-US/index.ts @@ -1 +1 @@ -export default { EN: 'EN', RU: 'RU', continue: 'Continue', back: 'Back', month: 'month', months: 'months', slogan: 'Work together - it\'s magic!', under_construction: 'Under construction.', login__email: 'E-mail', login__password: 'Password', login__forgot_password: 'Forgot Password?', login__sign_in: 'Log in', login__incorrect_login_data: 'User data not found. Edit your auth details before continuing', login__or_continue_as: 'or continue as', login__terms_of_use: 'Terms of use', login__accept_terms_of_use: 'I accept the', login__register: 'Create account', login__registration_message_error: 'Error', login__licensing_agreement: 'Licensing agreement', login__have_account: 'Already have an accont?', user__logout: 'Logout', projects__projects: 'Projects', projects__show_archive: 'Show archive', projects__hide_archive: 'Hide archive', projects__restore_archive_warning: 'Attention!', projects__restore_archive_warning_message: 'To restore a project from an archive, you must manually attach chats to it.', project__chats: 'Chats', project__persons: 'Persons', project__companies: 'Companies', project__edit: 'Edit', project__backup: 'Backup', project__archive: 'Archive', project__archive_warning: 'Are you sure?', project__archive_warning_message: 'Chat tracking in the project will be disabled after moving to the archive.', project__delete: 'Delete', project__delete_warning: 'Warning!', project__delete_warning_message: 'All project data will be removed. This action cannot be undone.', project_chats__search: 'Search', project_chats__send_chat: 'Request for attach chat', project_chats__send_chat_description: 'Provide instructions to the chat admin', project_chats__attach_chat: 'Attach chat', project_chats__attach_chat_description: 'Requires chat administrator privileges', project_chat__delete_warning: 'Warning!', project_chat__delete_warning_message: 'Chat tracking will be discontinued. If necessary, the cat can be attached again.', project_card__project_card: 'Project card', project_card__add_project: 'Add project', project_card__project_name: 'Name', project_card__project_description: 'Description', project_card__btn_accept: 'Accept', project_card__btn_back: 'Back', project_card__image_use_as_background_chats: 'logo as background for chats', project_card__error_name: 'Field is required', forgot_password__password_recovery: 'Password recovery', account_helper__enter_email: 'Enter account e-mail', account_helper__email: 'E-mail', account_helper__confirm_email: 'Confirm e-mail', account_helper__confirm_email_message: 'Enter the Code from e-mail to continue recover your password. If you haven\'t received an e-mail with the Code, check the Spam folder.', account_helper__code: 'Code', account_helper__code_error: 'Incorrect code. Ensure your e-mail is correct and try again.', account_helper__set_password: 'Set password', account_helper__password: 'Password', account_helper__finish: 'Finish', account_helper__finish_after_message: 'Done!', account__user_settings: 'User settings', account__your_company: 'Your company', account__change_auth_message_2: 'After creating a user, all data from the Telegram account will be transferred to the new account.', account__change_auth_btn: 'Create system account', account__change_auth_warning: 'WARNING!', account__change_auth_warning_message: 'Reverse data transfer is not possible.', account__chats: 'Chats', account__chats_active: 'Active', account__chats_unbound: 'Unbound', account__chats_free: 'Free', account__chats_total: 'Total', account__subscribe: 'Subscribe', account__subscribe_description: 'With a subscription, you can attach more active chats.', account__auth_change_method: 'Change authorization method', account__auth_change_method_description: 'In case of corporate use, it is recommended to log in with a username and password.', account__auth_change_password: 'Change account password', account__auth_change_password_description: 'Access to the email address used for system login is required.', account__auth_change_account: 'Change account e-mail', account__auth_change_account_description: 'Access to both the current and new email addresses used for system authentication is required.', account__company_data: 'Your company data', account__company_data_description: 'Projects will automatically include this data.', account__manual: 'Manual', account__manual_description: 'Go to our Telegram channel with video tutorials.', account__support: 'Support', account__support_description: 'Need help? Contact us!', account__terms_of_use: 'Terms of use', account__privacy: 'Privacy and Cookie Policy', company__mask: 'Company cloacking', mask__title_table: 'Excluded', mask__help_title: 'Cloacking', mask__help_message: 'It is possible to cloacking a company by representing its personnel as your own to companies other than those on the exclusion list.', company_info__title_card: 'Company card', company_info__name: 'Name', company_info__description: 'Description', company_info__persons: 'Persons', company_create__title_card: 'Add company', project_persons__search: 'Search', person_card__title: 'Person card', person_card__name: 'Name', person_card__company: 'Company name', person_card__department: 'Department', person_card__role: 'Role', settings__title: 'Settings', settings__language: 'Language', settings__font_size: 'Font size', terms__title: 'Terms of use', subscribe__title: 'Subscribe', subscribe__current_balance: 'Current balance', subscribe__token_formula: '1 = 1 day of access to 1 chat', subscribe__token_formula_description: 'unbound and free chats are not counted', subscribe__info: 'With a subscription, you can attach more chats. Archived chats are not counted.', subscribe__about: 'about', subscribe__select_payment_1: 'You can pay for your subscription using ', subscribe__select_payment_2: 'Telegram stars', subscribe__select_option_1: 'Telegram stars', subscribe__select_option_2: 'Telegram stars', subscribe__select_option_3: 'Telegram stars', subscribe__select_option_user: 'Telegram stars' } \ No newline at end of file +export default { EN: 'EN', RU: 'RU', continue: 'Continue', back: 'Back', close: 'Close', month: 'month', months: 'months', slogan: 'Work together - it\'s magic!', under_construction: 'Under construction.', B: 'B', kB: 'kB', MB: 'MB', GB: 'GB', TB: 'TB', main__chats: 'Chats', main__tasks: 'Tasks', main__meetings: 'Meetings', main__files: 'Files', main__users: 'Contacts', chats__search: 'Search', tasks__search: 'Search', tasks__filters: 'Filters', tasks__filters_types: 'Task types', tasks__filters_in: 'Assigned to me', tasks__filters_out: 'Assigned by me', tasks__filters_watch: 'Following', tasks__filters_priority: 'Task priority', tasks__filters_priority_normal: 'Normal', tasks__filters_priority_important: 'Important', tasks__filters_priority_critical: 'Critical', task_add__title: 'Create task', task_add__name: 'Title', task_add__description: 'Description', task_add__plan_date: 'Planed date', task_add__priority: 'Priority', task_add__priority_normal: 'Normal', task_add__priority_important: 'Important', task_add__priority_critical: 'Critical', task_add__attached_chat: 'Attached chat', task_add__attach_files: 'Attached files', task_add__assigned_to: 'Assignee', task_add__watch: 'Followers', meetings__search: 'Search', meetings__previous: 'Previous', meetings__previous_hide: 'Hide', meeting_create__title_card: 'Create meeting', meeting_edit__title_card: 'Edit meeting', meeting_view__title_card: 'Meeting card', meeting_info__name: 'Title', meeting_info__description: 'Description', meeting_info__date: 'Date', meeting_info__time: 'Time', meeting_info__attach_chat: 'Attach to chat', meeting_info__participants: 'Participants', meeting_info__attach_files: 'Files', meeting_info__canceled: 'Canceled', meeting_info__dialog_cancel_title: 'Cancel the meeting?', meeting_info__dialog_cancel_ok: 'Confirm', meeting_info__dialog_cancel_delete: 'Delete', meeting_info__dialog_restore_title: 'Restore the meeting?', meeting_info__dialog_restore_ok: 'Confirm', files__search: 'Search', files__filters: 'Filters', files__filters_extension: 'Extensions (types)', files__filters_source: 'Source', files__filters_source_chats: 'chat', files__filters_source_tasks: 'task', files__filters_source_meetings: 'meeting', files__filters_by: 'Author', files__filters_size: 'Size', files__filters_size_small: 'small (less than 5MB)', files__filters_size_middle: 'middle (5-25MB)', files__filters_size_big: 'big (25-100MB)', files__filters_size_very_big: 'very big (more 100MB)', files_filters_reset: 'Reset filters', header__my_projects: 'My projects', header__all_projects: 'All projects', users__search: 'Search', user_card__title: 'User card', user_card__name: 'Name', user_card__phone: 'Phone', user_card__email: 'Email', user_card__position: 'Position', settings__title: 'Settings', settings__language: 'Language', settings__font_size: 'Font size' } \ No newline at end of file diff --git a/src/i18n/ru-RU/index.ts b/src/i18n/ru-RU/index.ts index e3dd6da..72da05d 100644 --- a/src/i18n/ru-RU/index.ts +++ b/src/i18n/ru-RU/index.ts @@ -1 +1 @@ -export default { EN: 'EN', RU: 'RU', continue: 'Продолжить', back: 'Назад', month: 'мес.', months: 'мес.', slogan: 'Работайте вместе - это волшебство!', under_construction: 'В разработке.', login__email: 'Электронная почта', login__password: 'Пароль', login__forgot_password: 'Забыли пароль?', login__sign_in: 'Войти', login__incorrect_login_data: 'Пользователь с такими данными не найден. Отредактируйте введенные данные', login__or_continue_as: 'или продолжить', login__terms_of_use: 'Пользовательское соглашение', login__accept_terms_of_use: 'Я принимаю', login__register: 'Зарегестрироваться', login__registration_message_error: 'Ошибка', login__licensing_agreement: 'Договор о лицензировании', login__have_account: 'Есть учетная запись', user__logout: 'Выход', projects__projects: 'Проекты', projects__show_archive: 'Показать архив', projects__hide_archive: 'Скрыть архив', projects__restore_archive_warning: 'Внимание!', projects__restore_archive_warning_message: 'При восстановлении проекта из архива - присоединение чатов к проекту требуется осуществлять вручную.', project__chats: 'Чаты', project__persons: 'Люди', project__companies: 'Компании', project__edit: 'Редактировать', project__backup: 'Резервная копия', project__archive: 'В архив', project__archive_warning: 'Вы уверены?', project__archive_warning_message: 'После перемещения проекта в архив отслеживание чатов будет отключено.', project__delete: 'Удалить', project__delete_warning: 'Внимание!', project__delete_warning_message: 'Все данные проекта будут безвозвратно удалены.', project_chats__search: 'Поиск', project_chats__send_chat: 'Запрос на добавление чата', project_chats__send_chat_description: 'Отправить инструкцию администратору чата', project_chats__attach_chat: 'Добавить чат', project_chats__attach_chat_description: 'Необходимы права администратора чата', project_chat__delete_warning: 'Внимание!', project_chat__delete_warning_message: 'Отслеживание чата будет прекращено. При необходимости чат можно будет подключить снова.', project_card__project_card: 'Карточка компании', project_card__add_project: 'Новый проект', project_card__project_name: 'Название', project_card__project_description: 'Описание', project_card__btn_accept: 'Подтвердить', project_card__btn_back: 'Назад', project_card__image_use_as_background_chats: 'логотип в качестве фона для чатов', project_card__error_name: 'Поле обязательно к заполнению', forgot_password__password_recovery: 'Восстановление пароля', account_helper__enter_email: 'Введите электронную почту', account_helper__email: 'Электронная почта', account_helper__confirm_email: 'Подтверждение электронной почты', account_helper__confirm_email_message: 'Введите код из письма для продолжения восстановления пароля. Если не получили письмо с кодом - проверьте папку Спам', account_helper__code: 'Код', account_helper__code_error: 'Был введен неверный код. Проверьте адрес электронной почты и повторите попытку.', account_helper__set_password: 'Установка пароля', account_helper__password: 'Пароль', account_helper__finish: 'Отправить', account_helper__finish_after_message: 'Готово!', account__user_settings: 'Пользовательские настройки', account__your_company: 'Ваша компания', account__change_auth_message_2: 'После создания пользователя все данные с учетной записи Telegram будут перенесены на новую учетную запись.', account__change_auth_btn: 'Создать пользователя', account__change_auth_warning: 'ВНИМАНИЕ!', account__change_auth_warning_message: 'Обратный перенос данных не возможен.', account__chats: 'Чаты', account__chats_active: 'Активные', account__chats_unbound: 'Открепленные', account__chats_free: 'Бесплатные', account__chats_total: 'Всего', account__subscribe: 'Подписка', account__subscribe_description: 'С помощью подписки можно подключить дополнительные чаты.', account__auth_change_method: 'Сменить способ авторизации', account__auth_change_method_description: 'В случае корпоративного использования рекомендуется входить в систему, указав логин и пароль.', account__auth_change_password: 'Изменить пользовательский пароль', account__auth_change_password_description: 'Необходим доступ к электронной почте, используемой для входа в систему.', account__auth_change_account: 'Сменить электронную почту учетной записи', account__auth_change_account_description: 'Необходим доступ к текущей и новой электронной почте, используемым для входа в систему.', account__company_data: 'Данные вашей компании', account__company_data_description: 'Эти данные будут автоматически подгружаться в проекты. ', account__manual: 'Инструкции', account__manual_description: 'Перейдите в наш Telegram-канал с обучающими видеороликами.', account__support: 'Поддержка', account__support_description: 'Есть вопросы - напишите нам!', account__terms_of_use: 'Пользовательское соглашение', account__privacy: 'Политика конфидециальности', company__mask: 'Маскировка компаний', mask__title_table: 'Исключения', mask__help_title: 'Маскировка', mask__help_message: 'Возможно замаскировать компанию, представляя ее персонал как собственный для других компаний, кроме тех, что есть в перечне исключений. ', company_info__title_card: 'Карточка компании', company_info__name: 'Название', company_info__description: 'Описание', company_info__persons: 'Сотрудники', company_create__title_card: 'Добавление компании', project_persons__search: 'Поиск', person_card__title: 'Карточка сотрудника', person_card__name: 'ФИО', person_card__company: 'Название компании', person_card__department: 'Подразделение', person_card__role: 'Функционал (должность)', settings__title: 'Настройки', settings__language: 'Язык', settings__font_size: 'Размер шрифта', terms__title: 'Пользовательское соглашение', subscribe__title: 'Подписка', subscribe__current_balance: 'Текущий баланс', subscribe__token_formula: '1 = 1 день подключения к 1 чату', subscribe__token_formula_description: 'отвязанные и бесплатные чаты не учитываются', subscribe__info: 'С помощью подписки можно подключить к бесплатным групповым чатам дополнительные. Архивные чаты не учитываются. ', subscribe__about: 'около', subscribe__select_payment_1: 'Вы можете оплатить подписку с помощью', subscribe__select_payment_2: 'Telegram stars', subscribe__select_option_1: 'Telegram stars', subscribe__select_option_2: 'Telegram stars', subscribe__select_option_3: 'Telegram stars', subscribe__select_option_user: 'Telegram stars' } \ No newline at end of file +export default { EN: 'EN', RU: 'RU', continue: 'Продолжить', back: 'Назад', close: 'Закрыть', month: 'мес.', months: 'мес.', slogan: 'Работайте вместе - это волшебство!', under_construction: 'В разработке.', B: 'Б', kB: 'КБ', MB: 'МБ', GB: 'ГБ', TB: 'ТБ', main__chats: 'Чаты', main__tasks: 'Задачи', main__meetings: 'Совещания', main__files: 'Файлы', main__users: 'Контакты', chats__search: 'Поиск', tasks__search: 'Поиск', tasks__filters: 'Фильтры', tasks__filters_types: 'Типы задач', tasks__filters_in: 'Входящие', tasks__filters_out: 'Порученные', tasks__filters_watch: 'Отслеживаемые', tasks__filters_priority: 'Приоритет задач', tasks__filters_priority_normal: 'Нормальный', tasks__filters_priority_important: 'Важный', tasks__filters_priority_critical: 'Критичный', task_add__title: 'Создание задачи', task_add__name: 'Заголовок', task_add__description: 'Описание', task_add__plan_date: 'Дата выполнения', task_add__priority: 'Приоритет', task_add__priority_normal: 'Нормальный', task_add__priority_important: 'Важный', task_add__priority_critical: 'Критичный', task_add__attached_chat: 'Связанный чат', task_add__attach_files: 'Прикрепленные файлы', task_add__assigned_to: 'Ответственный', task_add__watch: 'Наблюдатели', meetings__search: 'Поиск', meetings__previous: 'Прошедшие', meetings__previous_hide: 'Сбросить', meeting_create__title_card: 'Создать совещание', meeting_edit__title_card: 'Редактировать совещание', meeting_view__title_card: 'Карточка совещания', meeting_info__name: 'Тема', meeting_info__description: 'Описание', meeting_info__date: 'Дата', meeting_info__time: 'Время', meeting_info__attach_chat: 'Прикрепить к чату', meeting_info__participants: 'Участники', meeting_info__attach_files: 'Файлы', meeting_info__canceled: 'Отменено', meeting_info__dialog_cancel_title: 'Отменить совещание?', meeting_info__dialog_cancel_ok: 'Подтвердить', meeting_info__dialog_cancel_delete: 'Удалить', meeting_info__dialog_restore_title: 'Возобновить совещание?', meeting_info__dialog_restore_ok: 'Подтвердить', files__search: 'Поиск', files__filters: 'Фильтры', files__filters_extension: 'Расширения (типы)', files__filters_source: 'Источник ', files__filters_source_chats: 'чат', files__filters_source_tasks: 'задача', files__filters_source_meetings: 'совещание', files__filters_by: 'Автор', files__filters_size: 'Размер', files__filters_size_small: 'небольшой (менее 5 МБ)', files__filters_size_middle: 'средний (5-25МБ)', files__filters_size_big: 'большой (25-100МБ)', files__filters_size_very_big: 'очень большой (более 100МБ)', files_filters_reset: 'Сбросить фильтры', header__my_projects: 'Мои проекты', header__all_projects: 'Все проекты', users__search: 'Поиск', user_card__title: 'Карточка пользователя', user_card__name: 'Имя', user_card__phone: 'Телефон', user_card__email: 'Электронная почта', user_card__position: 'Позиция', settings__title: 'Настройки', settings__language: 'Язык', settings__font_size: 'Размер шрифта' } \ No newline at end of file diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 40a112d..934b0b0 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -1,60 +1,24 @@ diff --git a/src/pages/AccountChangeEmailPage.vue b/src/pages/AccountChangeEmailPage.vue deleted file mode 100644 index 73d4a21..0000000 --- a/src/pages/AccountChangeEmailPage.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/src/pages/AccountChangePasswordPage.vue b/src/pages/AccountChangePasswordPage.vue deleted file mode 100644 index 73d4a21..0000000 --- a/src/pages/AccountChangePasswordPage.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/src/pages/AccountCreatePage.vue b/src/pages/AccountCreatePage.vue deleted file mode 100644 index 38ee95c..0000000 --- a/src/pages/AccountCreatePage.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - diff --git a/src/pages/AccountForgotPasswordPage.vue b/src/pages/AccountForgotPasswordPage.vue deleted file mode 100644 index 8a71e0b..0000000 --- a/src/pages/AccountForgotPasswordPage.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/src/pages/AccountPage.vue b/src/pages/AccountPage.vue deleted file mode 100644 index 2b0f59c..0000000 --- a/src/pages/AccountPage.vue +++ /dev/null @@ -1,88 +0,0 @@ - - - - - diff --git a/src/pages/CompanyCreatePage.vue b/src/pages/CompanyCreatePage.vue deleted file mode 100644 index 1b816ac..0000000 --- a/src/pages/CompanyCreatePage.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - diff --git a/src/pages/CompanyInfoPage.vue b/src/pages/CompanyInfoPage.vue deleted file mode 100644 index 8d1d56f..0000000 --- a/src/pages/CompanyInfoPage.vue +++ /dev/null @@ -1,74 +0,0 @@ - - - - diff --git a/src/pages/CompanyMaskPage.vue b/src/pages/CompanyMaskPage.vue deleted file mode 100644 index 9a51a34..0000000 --- a/src/pages/CompanyMaskPage.vue +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - diff --git a/src/pages/CompanyYourPage.vue b/src/pages/CompanyYourPage.vue deleted file mode 100644 index 2678853..0000000 --- a/src/pages/CompanyYourPage.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - diff --git a/src/pages/LoginPage.vue b/src/pages/LoginPage.vue deleted file mode 100644 index ca3cd5d..0000000 --- a/src/pages/LoginPage.vue +++ /dev/null @@ -1,215 +0,0 @@ - - - - - diff --git a/src/pages/ProjectPage.vue b/src/pages/MainPage.vue similarity index 53% rename from src/pages/ProjectPage.vue rename to src/pages/MainPage.vue index 57658b2..3db9800 100644 --- a/src/pages/ProjectPage.vue +++ b/src/pages/MainPage.vue @@ -6,10 +6,7 @@ - + - @@ -66,35 +72,31 @@ diff --git a/src/pages/MeetingEditPage.vue b/src/pages/MeetingEditPage.vue new file mode 100644 index 0000000..6e637f9 --- /dev/null +++ b/src/pages/MeetingEditPage.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/src/pages/MeetingInfoPage.vue b/src/pages/MeetingInfoPage.vue new file mode 100644 index 0000000..6e637f9 --- /dev/null +++ b/src/pages/MeetingInfoPage.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/src/pages/PersonInfoPage.vue b/src/pages/PersonInfoPage.vue deleted file mode 100644 index 75e5cda..0000000 --- a/src/pages/PersonInfoPage.vue +++ /dev/null @@ -1,113 +0,0 @@ - - - - - diff --git a/src/pages/PrivacyPage.vue b/src/pages/PrivacyPage.vue deleted file mode 100644 index bc1de39..0000000 --- a/src/pages/PrivacyPage.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - - - diff --git a/src/pages/ProjectCreatePage.vue b/src/pages/ProjectCreatePage.vue deleted file mode 100644 index 3540940..0000000 --- a/src/pages/ProjectCreatePage.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/src/pages/ProjectInfoPage.vue b/src/pages/ProjectInfoPage.vue deleted file mode 100644 index 0481631..0000000 --- a/src/pages/ProjectInfoPage.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - - - diff --git a/src/pages/ProjectsPage.vue b/src/pages/ProjectsPage.vue deleted file mode 100644 index 028a88c..0000000 --- a/src/pages/ProjectsPage.vue +++ /dev/null @@ -1,233 +0,0 @@ - - - - - diff --git a/src/pages/SettingsPage.vue b/src/pages/SettingsPage.vue index 84eebc2..cf7b5d2 100644 --- a/src/pages/SettingsPage.vue +++ b/src/pages/SettingsPage.vue @@ -1,11 +1,7 @@ + \ No newline at end of file diff --git a/src/pages/SubscribePage.vue b/src/pages/SubscribePage.vue deleted file mode 100644 index 6db5ca0..0000000 --- a/src/pages/SubscribePage.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - - - diff --git a/src/pages/TaskAddPage.vue b/src/pages/TaskAddPage.vue new file mode 100644 index 0000000..c308cbf --- /dev/null +++ b/src/pages/TaskAddPage.vue @@ -0,0 +1,236 @@ + + + + + diff --git a/src/pages/TaskInfoPage.vue b/src/pages/TaskInfoPage.vue new file mode 100644 index 0000000..84eebc2 --- /dev/null +++ b/src/pages/TaskInfoPage.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/pages/TermsPage.vue b/src/pages/TermsPage.vue deleted file mode 100644 index e70761f..0000000 --- a/src/pages/TermsPage.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - - - diff --git a/src/pages/UserInfoPage.vue b/src/pages/UserInfoPage.vue new file mode 100644 index 0000000..fe5884c --- /dev/null +++ b/src/pages/UserInfoPage.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/src/pages/main/ChatsPage.vue b/src/pages/main/ChatsPage.vue new file mode 100644 index 0000000..95668eb --- /dev/null +++ b/src/pages/main/ChatsPage.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/src/pages/main/FilesPage.vue b/src/pages/main/FilesPage.vue new file mode 100644 index 0000000..1b27f20 --- /dev/null +++ b/src/pages/main/FilesPage.vue @@ -0,0 +1,504 @@ + + + + + diff --git a/src/pages/main/HeaderPage.vue b/src/pages/main/HeaderPage.vue new file mode 100644 index 0000000..4f7322d --- /dev/null +++ b/src/pages/main/HeaderPage.vue @@ -0,0 +1,170 @@ + + + + + + + + diff --git a/src/pages/main/MeetingsPage.vue b/src/pages/main/MeetingsPage.vue new file mode 100644 index 0000000..d3477b2 --- /dev/null +++ b/src/pages/main/MeetingsPage.vue @@ -0,0 +1,545 @@ + + + + + diff --git a/src/pages/main/TasksPage.vue b/src/pages/main/TasksPage.vue new file mode 100644 index 0000000..650fdaa --- /dev/null +++ b/src/pages/main/TasksPage.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/src/pages/main/UsersPage.vue b/src/pages/main/UsersPage.vue new file mode 100644 index 0000000..536172d --- /dev/null +++ b/src/pages/main/UsersPage.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/src/router/index.ts b/src/router/index.ts index d77f8b4..a346060 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -6,18 +6,8 @@ import { createWebHistory, } from 'vue-router' import routes from './routes' -import { useAuthStore } from 'stores/auth' import { useProjectsStore } from 'stores/projects' -/* - * If not building with SSR mode, you can - * directly export the Router instantiation; - * - * The function below can be async too; either use - * async/await or return a Promise which resolves - * with the Router instance. - */ - export default defineRouter(function (/* { store, ssrContext } */) { const createHistory = process.env.SERVER ? createMemoryHistory @@ -32,30 +22,28 @@ export default defineRouter(function (/* { store, ssrContext } */) { // quasar.conf.js -> build -> publicPath history: createHistory(process.env.VUE_ROUTER_BASE), }) - - const publicPaths = ['/login', '/create-account', '/recovery-password'] Router.beforeEach(async (to) => { - const authStore = useAuthStore() + console.log(to) + if (to.name === 'settings') return; + const projectsStore = useProjectsStore() - // Инициализация хранилища перед проверкой - if (!authStore.isInitialized) { - await authStore.initialize() - } - - // Проверка авторизации для непубличных маршрутов - if (!publicPaths.includes(to.path)) { - if (!authStore.isAuthenticated) { - return { - path: '/login', - query: { redirect: to.fullPath } - } + if (to.params.id) { + const projectId = Number(to.params.id) + + if (!projectsStore.isInit) await projectsStore.init() + + const project = projectsStore.projectById(projectId) + if (!project) return { name: 'page404' } + + if (projectsStore.currentProjectId !== projectId) { + projectsStore.setCurrentProjectId(projectId) } - } - - // Редирект авторизованных пользователей с публичных маршрутов - if (publicPaths.includes(to.path) && authStore.isAuthenticated) { - return { path: '/' } + + } else { + if (!projectsStore.startProjectId) return { name: 'page404' } + projectsStore.setCurrentProjectId(projectsStore.startProjectId) + return { name: 'files', params: { id: projectsStore.startProjectId }} } }) @@ -67,11 +55,10 @@ export default defineRouter(function (/* { store, ssrContext } */) { if (window.history.length > 1) { Router.go(-1) } else { - await Router.push('/projects') + await Router.push({ name: 'main'}) } } } - Router.afterEach((to) => { const BackButton = window.Telegram?.WebApp?.BackButton; @@ -87,11 +74,6 @@ export default defineRouter(function (/* { store, ssrContext } */) { BackButton.offClick(handleBackButton as () => void) BackButton.onClick(handleBackButton as () => void) } - - if (!to.params.id) { - const projectsStore = useProjectsStore() - projectsStore.setCurrentProjectId(null) - } }) return Router diff --git a/src/router/routes.ts b/src/router/routes.ts index 108ce26..39d66c8 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -1,160 +1,83 @@ -import type { RouteRecordRaw, RouteLocationNormalized } from 'vue-router' -import { useProjectsStore } from '../stores/projects' - -const setProjectBeforeEnter = (to: RouteLocationNormalized) => { - const id = Number(to.params.id) - const projectsStore = useProjectsStore() - projectsStore.setCurrentProjectId( - !isNaN(id) && projectsStore.projectById(id) ? id : null - ) -} +import type { RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/', component: () => import('layouts/MainLayout.vue'), children: [ - { - path: '', - redirect: '/projects' - }, - { - name: 'projects', - path: '/projects', - component: () => import('pages/ProjectsPage.vue'), - meta: { hideBackButton: true } - }, - { - name: 'project_add', - path: '/project/add', - component: () => import('pages/ProjectCreatePage.vue') - }, - - { - name: 'project_info', - path: '/project/:id(\\d+)/info', - component: () => import('pages/ProjectInfoPage.vue'), - beforeEnter: setProjectBeforeEnter - }, - { - name: 'company_mask', - path: '/project/:id(\\d+)/company-mask', - component: () => import('pages/CompanyMaskPage.vue'), - beforeEnter: setProjectBeforeEnter - }, - { path: '/project/:id(\\d+)', - component: () => import('pages/ProjectPage.vue'), - beforeEnter: setProjectBeforeEnter, + component: () => import('pages/MainPage.vue'), + redirect: { name: 'files' }, + meta: { hideBackButton: true }, children: [ { - name: 'project', - path: '', - redirect: { name: 'chats' } + name: 'files', + path: 'files', + component: () => import('pages/main/FilesPage.vue'), + meta: { hideBackButton: true } + }, + { + name: 'tasks', + path: 'tasks', + component: () => import('pages/main/TasksPage.vue'), + meta: { hideBackButton: true } + }, + { + name: 'meetings', + path: 'meetings', + component: () => import('pages/main/MeetingsPage.vue'), + meta: { hideBackButton: true } + }, + { + name: 'users', + path: 'users', + component: () => import('src/pages/main/UsersPage.vue'), + meta: { hideBackButton: true } }, { name: 'chats', path: 'chats', - component: () => import('components/admin/project-page/ProjectPageChats.vue'), - meta: { backRoute: '/projects' } - }, - { - name: 'persons', - path: 'persons', - component: () => import('components/admin/project-page/ProjectPagePersons.vue'), - meta: { backRoute: '/projects' } - }, - { - name: 'companies', - path: 'companies', - component: () => import('components/admin/project-page/ProjectPageCompanies.vue'), - meta: { backRoute: '/projects' } + component: () => import('pages/main/ChatsPage.vue'), + meta: { hideBackButton: true } } ] }, { - name: 'company_info', - path: '/project/:id(\\d+)/company/:companyId', - component: () => import('pages/CompanyInfoPage.vue'), - beforeEnter: setProjectBeforeEnter + name: 'task_add', + path: '/project/:id(\\d+)/task/add', + component: () => import('pages/TaskAddPage.vue') }, { - name: 'person_info', - path: '/project/:id(\\d+)/person/:personId', - component: () => import('pages/PersonInfoPage.vue'), - beforeEnter: setProjectBeforeEnter + name: 'task_info', + path: '/project/:id(\\d+)/task/:taskId(\\d+)', + component: () => import('pages/TaskInfoPage.vue'), + }, + { + name: 'meeting_add', + path: '/project/:id(\\d+)/meeting/add', + component: () => import('pages/MeetingAddPage.vue') + }, + { + name: 'meeting_edit', + path: '/project/:id(\\d+)/meeting/:meetingId(\\d+)', + component: () => import('pages/MeetingEditPage.vue'), + }, + { + name: 'meeting_info', + path: '/project/:id(\\d+)/meeting/:meetingId(\\d+)', + component: () => import('pages/MeetingInfoPage.vue'), + }, + { + name: 'user_info', + path: '/project/:id(\\d+)/user/:userId', + component: () => import('pages/UserInfoPage.vue'), }, - { - name: 'account', - path: '/account', - component: () => import('pages/AccountPage.vue') - }, - { - name: 'create_account', - path: '/create-account', - component: () => import('src/pages/AccountCreatePage.vue') - }, - { - name: 'change_account_password', - path: '/change-password', - component: () => import('pages/AccountChangePasswordPage.vue') - }, - { - name: 'change_account_email', - path: '/change-email', - component: () => import('pages/AccountChangeEmailPage.vue') - }, - { - name: 'subscribe', - path: '/subscribe', - component: () => import('pages/SubscribePage.vue') - }, - { - name: 'terms', - path: '/terms-of-use', - component: () => import('pages/TermsPage.vue') - }, - { - name: 'privacy', - path: '/privacy', - component: () => import('pages/PrivacyPage.vue') - }, - { - name: 'your_company', - path: '/your-company', - component: () => import('src/pages/CompanyYourPage.vue') - }, - { - name: 'login', - path: '/login', - component: () => import('pages/LoginPage.vue') - }, - - { - name: 'recovery_password', - path: '/recovery-password', - component: () => import('src/pages/AccountForgotPasswordPage.vue') - }, - - { - name: 'add_company', - path: '/add-company', - component: () => import('src/pages/CompanyCreatePage.vue') - }, - - { - name: 'person_info', - path: '/person-info', - component: () => import('pages/PersonInfoPage.vue') - }, - - { name: 'settings', path: '/settings', component: () => import('pages/SettingsPage.vue') - } + } ] }, { @@ -163,5 +86,4 @@ const routes: RouteRecordRaw[] = [ } ] - -export default routes +export default routes \ No newline at end of file diff --git a/src/stores/auth.ts b/src/stores/auth.ts deleted file mode 100644 index a048f00..0000000 --- a/src/stores/auth.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import { api } from 'boot/axios' - -interface User { - id: string - email?: string - username: string - first_name?: string - last_name?: string - avatar?: string -} - -export const useAuthStore = defineStore('auth', () => { - // State - const user = ref(null) - const isInitialized = ref(false) - - // Getters - const isAuthenticated = computed(() => !!user.value) - - // Actions - const initialize = async () => { - try { - const { data } = await api.get('/customer/profile') - user.value = data - } catch (error) { - console.error(error) - user.value = null - } finally { - isInitialized.value = true - } - } - - const loginWithCredentials = async (email: string, password: string) => { - // будет переделано на беке - нужно сменить урл - await api.post('/api/admin/customer/login', { email, password }, { withCredentials: true }) - await initialize() - } - - const loginWithTelegram = async (initData: string) => { - await api.post('/api/admin/customer/login', { initData }, { withCredentials: true }) - await initialize() - } - - const logout = async () => { - try { - await api.get('/customer/logout', {}) - } finally { - user.value = null - // @ts-expect-ignore - // window.Telegram?.WebApp.close() - } - } - - return { - user, - isAuthenticated, - isInitialized, - initialize, - loginWithCredentials, - loginWithTelegram, - logout - } -}) \ No newline at end of file diff --git a/src/stores/chats.ts b/src/stores/chats.ts index 94e0ff8..a4ef668 100644 --- a/src/stores/chats.ts +++ b/src/stores/chats.ts @@ -1,36 +1,38 @@ -import { ref } from 'vue' +import { ref, computed } from 'vue' import { defineStore } from 'pinia' -import type { Chat } from '../types' +import { api } from 'boot/axios' +import { useProjectsStore } from 'stores/projects' +import type { Chat } from 'types/Chat' export const useChatsStore = defineStore('chats', () => { - const chats = ref([]) - - chats.value.push( - {id: 11, name: 'Аудит ИБ', logo: 'https://cdn.quasar.dev/img/avatar5.jpg', persons: 8, owner_id: 111}, - {id: 12, name: 'Разработка BI', logo: '', persons: 2, owner_id: 111}, - {id: 3, name: '-Обсуждение дашбордов', logo: '', description: 'Какой-то кратенькое описание', persons: 4, owner_id: 112}, - {id: 4, name: 'Расстрел нерадивых', logo: '', persons: 3, owner_id: 113}, - {id: 15, name: 'фыфыы Расстрел нерадивых', logo: '', persons: 5, owner_id: 112}, - {id: 16, name: 'Разработка BI', logo: '', persons: 6, owner_id: 114}, - {id: 17, name: '-Обсуждение дашбордов', logo: '', description: 'Какой-то кратенькое описание', persons: 58, owner_id: 111}, - {id: 18, name: 'Расстрел нерадивых', logo: '', persons: 3, owner_id: 112}, - {id: 19, name: 'фыфыы Расстрел нерадивых', logo: '', persons: 11, owner_id: 113}, - {id: 20, name: 'Разработка BI', logo: '', persons: 18, owner_id: 114}, - {id: 113, name: '-Обсуждение дашбордов', logo: '', description: 'Какой-то кратенькое описание', persons: 11, owner_id: 115}, - {id: 124, name: 'Расстрел нерадивых', logo: '', persons: 12, owner_id: 113}, - {id: 217, name: 'фыфыы Расстрел нерадивых', logo: '', persons: 5, owner_id: 112}, - {id: 2113, name: '-Обсуждение дашбордов', logo: '', description: 'Какой-то кратенькое описание', persons: 4, owner_id: 111}, - {id: 124, name: 'Расстрел нерадивых', logo: '', persons: 3, owner_id: 112}, - {id: 2117, name: 'фыфыы Расстрел нерадивых', logo: '', persons: 5, owner_id: 111}, - ) - function chatById (id :number) { + const chats = ref([]) + const isInit = ref(false) + + const projectsStore = useProjectsStore() + const currentProjectId = computed(() => projectsStore.currentProjectId) + + async function init () { + const response = await api.get('/project/' + currentProjectId.value + '/chat') + const chatsAPI = response.data.data + chats.value.push(...chatsAPI) + isInit.value = true + } + + function reset () { + chats.value = [] + isInit.value = false + } + + function chatById (id: number) { return chats.value.find(el =>el.id === id) } - function deleteChat (id :number) { - const idx = chats.value.findIndex(item => item.id === id) - chats.value.splice(idx, 1) - } - return { chats, deleteChat, chatById } + return { + chats, + isInit, + init, + reset, + chatById + } }) diff --git a/src/stores/companies.ts b/src/stores/companies.ts deleted file mode 100644 index 289431f..0000000 --- a/src/stores/companies.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ref } from 'vue' -import { defineStore } from 'pinia' -import type { Company, CompanyParams } from '../types' - -export const useCompaniesStore = defineStore('companies', () => { - const companies = ref([]) - - companies.value.push( - {id: 11, project_id: 11, name: 'Рога и копытца', logo: '', description: 'Монтажники вывески' }, - {id: 21, project_id: 12, name: 'ООО "Василек33"', logo: '' }, - {id: 13, project_id: 13, name: 'Откат и деньги', logo: '', description: 'Договариваются с администрацией' }, - ) - - function companyById (id :number) { - return companies.value.find(el =>el.id === id) - } - - function addCompany (company: CompanyParams) { - companies.value.push({ - id: Date.now(), - project_id: Date.now() * 1000, - ...company - }) - } - - function updateCompany (id :number, company: CompanyParams) { - const idx = companies.value.findIndex(item => item.id === id) - Object.assign(companies.value[idx] || {}, company) - } - - function deleteCompany (id :number) { - const idx = companies.value.findIndex(item => item.id === id) - companies.value.splice(idx, 1) - } - - return { companies, addCompany, updateCompany, deleteCompany, companyById } -}) diff --git a/src/stores/files.ts b/src/stores/files.ts new file mode 100644 index 0000000..9eacac1 --- /dev/null +++ b/src/stores/files.ts @@ -0,0 +1,51 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' +import { api } from 'boot/axios' +import { useProjectsStore } from 'stores/projects' +import type { File } from 'types/File' + +export const useFilesStore = defineStore('files', () => { + + const files = ref([]) + const isInit = ref(false) + + const projectsStore = useProjectsStore() + const currentProjectId = computed(() => projectsStore.currentProjectId) + + async function init () { + const response = await api.get('/project/' + currentProjectId.value + '/file') + const filesAPI = response.data.data + files.value.push(...filesAPI) + isInit.value = true + } + + function reset () { + files.value = [] + isInit.value = false + } + + async function fileUrl (fileId: number) { + const response = api.get('/project/' + currentProjectId.value + '/file/' + fileId) + return (await response).data.data + } + + async function remove (fileId: number) { + const response = api.delete('/project/' + currentProjectId.value + '/file/' + fileId) + return (await response).data.data + } + + + function fileById (id: number) { + return files.value.find(el =>el.id === id) + } + + return { + files, + isInit, + init, + reset, + fileUrl, + remove, + fileById + } +}) diff --git a/src/stores/meetings.ts b/src/stores/meetings.ts new file mode 100644 index 0000000..9a280e9 --- /dev/null +++ b/src/stores/meetings.ts @@ -0,0 +1,86 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' +import { api } from 'boot/axios' +import { useProjectsStore } from 'stores/projects' +import type { Meeting, MeetingParams } from 'types/Meeting' + +export const useMeetingsStore = defineStore('meetings', () => { + + const meetings = ref([]) + const isInit = ref(false) + + const projectsStore = useProjectsStore() + const currentProjectId = computed(() => projectsStore.currentProjectId) + + async function init () { + const response = await api.get('/project/' + currentProjectId.value + '/meeting') + const meetingsAPI = response.data.data + meetings.value.push(...meetingsAPI) + isInit.value = true + } + + function reset () { + meetings.value = [] + isInit.value = false + } + + async function add (meetingData: MeetingParams) { + const response = await api.post('/project/' + currentProjectId.value + '/meeting', meetingData) + const newMeetingAPI = response.data.data + meetings.value.push(newMeetingAPI) + return newMeetingAPI + } + + async function update (meetingId: number, meetingData: MeetingParams) { + const response = await api.put('/project/' + currentProjectId.value + '/meeting/' + meetingId, meetingData) + const meetingAPI = response.data.data + const idx = meetings.value.findIndex(item => item.id === meetingAPI.id) + if (meetings.value[idx]) Object.assign(meetings.value[idx], meetingAPI) + } + + async function updateParticipants (meetingId: number, participants: number[]) { + const response = await api.put('/project/' + currentProjectId.value + '/meeting/' + meetingId + '/participant', participants) + const participantsAPI = response.data.data + const idx = meetings.value.findIndex(item => item.id === meetingId) + if (meetings.value[idx]) meetings.value[idx].participants = participantsAPI + } + + async function attachFiles (meetingId: number, files: number[]) { + const response = await api.put('/project/' + currentProjectId.value + '/meeting/' + meetingId + '/attach', files) + const filesAPI = response.data.data + const idx = meetings.value.findIndex(item => item.id === meetingId) + if (meetings.value[idx]) meetings.value[idx].files = filesAPI + } + + async function setCancelStatus (meetingId: number, status: boolean) { + const response = await api.put('/project/' + currentProjectId.value + '/meeting/' + meetingId, { is_cancel: status }) + const meetingAPI = response.data.data + const idx = meetings.value.findIndex(item => item.id === meetingAPI.id) + if (meetings.value[idx]) Object.assign(meetings.value[idx], meetingAPI) + } + + async function remove (meetingId: number) { + const response = await api.delete('/project/' + currentProjectId.value + '/meeting/' + meetingId) + const meetingAPIid = response.data.data.id + const idx = meetings.value.findIndex(item => item.id === meetingAPIid) + meetings.value.splice(idx, 1) + } + + function meetingById (id :number) { + return meetings.value.find(el => el.id === id) + } + + return { + meetings, + isInit, + init, + reset, + add, + update, + updateParticipants, + attachFiles, + setCancelStatus, + remove, + meetingById + } +}) diff --git a/src/stores/projects.ts b/src/stores/projects.ts index e9b0c91..6af0459 100644 --- a/src/stores/projects.ts +++ b/src/stores/projects.ts @@ -1,77 +1,85 @@ -import { ref } from 'vue' +import { ref, watch } from 'vue' import { defineStore } from 'pinia' -import type { Project, ProjectParams } from '../types' +import { api } from 'boot/axios' + +import { useFilesStore } from 'stores/files' +import { useTasksStore } from 'stores/tasks' +import { useMeetingsStore } from 'stores/meetings' +import { useUsersStore } from 'stores/users' +import { useChatsStore } from 'stores/chats' + +import type { Project } from 'types/Project' export const useProjectsStore = defineStore('projects', () => { const projects = ref([]) const currentProjectId = ref(null) - - projects.value.push( - { id: 1, name: 'Тестовый проект', description: 'Пример тестового проекта - тут описание чего-то', logo: 'https://cdn.quasar.dev/img/boy-avatar.png', chats: 3, companies: 1, persons: 5, is_archive: false, logo_as_bg: false }, - { id: 2, name: 'Разделка бобра на куски', description: 'Пример тестового проекта - тут описание чего-то', logo: '', chats: 8, companies: 12, persons: 1, is_archive: false, logo_as_bg: false }, - { id: 3, name: 'Комплекс мер', description: '', logo: '', chats: 8, companies: 3, persons: 4, is_archive: true, logo_as_bg: false }, - { id: 4, name: 'Тестовый проект 2', description: 'Пример тестового проекта - тут описание чего-то', logo: '', chats: 12, companies: 11, persons: 15, is_archive: false, logo_as_bg: false }, - { id: 11, name: 'Тестовый проект 12', description: 'Пример тестового проекта - тут описание чего-то', logo: 'https://cdn.quasar.dev/img/boy-avatar.png', chats: 5, companies: 2, persons: 5, is_archive: false, logo_as_bg: false }, - { id: 12, name: 'Разделка бобра на куски 11 Ох как много кусков пипец каааак много - резать тяжело', description: '', logo: '', chats: 8, companies: 3, persons: 1, is_archive: false, logo_as_bg: false }, - { id: 13, name: 'Тестовый проект и что-то еще', description: 'Пример тестового проекта - тут описание чего-то Ох как много кусков пипец каааак много - резать тяжело Ох как много кусков пипец каааак много - резать тяжело', logo: 'https://cdn.quasar.dev/img/mountains.jpg', chats: 8, companies: 3, persons: 4, is_archive: false, logo_as_bg: true }, - { id: 14, name: 'Тестовый проект', description: 'Пример тестового проекта - тут описание чего-то', logo: 'https://cdn.quasar.dev/img/mountains.jpg', chats: 12, companies: 11, persons: 15, is_archive: false, logo_as_bg: false }, - { id: 112, name: 'Разделка бобра на куски 11 Ох как много кусков пипец каааак много - резать тяжело', description: '', logo: '', chats: 8, companies: 3, persons: 1, is_archive: false, logo_as_bg: false}, - { id: 113, name: 'Тестовый проект и что-то еще', description: 'Пример тестового проекта - тут описание чего-то Ох как много кусков пипец каааак много - резать тяжело Ох как много кусков пипец каааак много - резать тяжело', logo: 'https://cdn.quasar.dev/img/mountains.jpg', chats: 8, companies: 3, persons: 4, is_archive: false, logo_as_bg: false }, - { id: 114, name: 'Тестовый проект', description: 'Пример тестового проекта - тут описание чего-то', logo: 'https://cdn.quasar.dev/img/mountains.jpg', chats: 12, companies: 11, persons: 15, is_archive: true, logo_as_bg: false }, - { id: 1112, name: 'Разделка бобра на куски 11 Ох как много кусков пипец каааак много - резать тяжело', description: '', logo: '', chats: 8, companies: 3, persons: 1, is_archive: false, logo_as_bg: false }, - { id: 1113, name: 'Тестовый проект и что-то еще', description: 'Пример тестового проекта - тут описание чего-то Ох как много кусков пипец каааак много - резать тяжело Ох как много кусков пипец каааак много - резать тяжело', logo: 'https://cdn.quasar.dev/img/mountains.jpg', chats: 8, companies: 3, persons: 4, is_archive: false, logo_as_bg: false }, - { id: 1114, name: 'Тестовый проект', description: 'Пример тестового проекта - тут описание чего-то', logo: 'https://cdn.quasar.dev/img/mountains.jpg', chats: 12, companies: 11, persons: 15, is_archive: false, logo_as_bg: false }, - ) + const startProjectId = ref(null) + const isInit = ref(false) - function projectById (id :number) { + const filesStore = useFilesStore() + const tasksStore = useTasksStore() + const meetingsStore = useMeetingsStore() + const usersStore = useUsersStore() + const chatsStore = useChatsStore() + + async function init () { + const response = await api.get('/project') + const projectsAPI = response.data.data + projects.value.push(...projectsAPI) + isInit.value = true + } + + function reset () { + projects.value = [] + isInit.value = false + currentProjectId.value = null + } + + function projectById (id: number) { return projects.value.find(el =>el.id === id) } - - function addProject (project: ProjectParams) { - const newProject = { - id: Date.now(), - is_archive: false, - chats: 0, - persons: 0, - companies: 0, - ...project - } - projects.value.push(newProject) - return newProject - } - - function updateProject (id :number, project :Project) { - const idx = projects.value.findIndex(item => item.id === id) - Object.assign(projects.value[idx] || {}, project) - } - - function archiveProject (id :number, status :boolean) { - const idx = projects.value.findIndex(item => item.id === id) - if (projects.value[idx]) projects.value[idx].is_archive = status - } - - function deleteProject (id :number) { - const idx = projects.value.findIndex(item => item.id === id) - projects.value.splice(idx, 1) - } function setCurrentProjectId (id: number | null) { currentProjectId.value = id } - function getCurrentProject () { - return currentProjectId.value ? projectById(currentProjectId.value) : {} + function setStartProjectId (id: number | null) { + startProjectId.value = id } + async function initStores () { + resetStores() + if (!filesStore.isInit) await filesStore.init() + if (!tasksStore.isInit) await tasksStore.init() + if (!meetingsStore.isInit) await meetingsStore.init() + if (!usersStore.isInit) await usersStore.init() + if (!chatsStore.isInit) await chatsStore.init() + + } + + function resetStores () { + filesStore.reset() + tasksStore.reset() + meetingsStore.reset() + usersStore.reset() + chatsStore.reset() + } + + watch (currentProjectId, async (newId) => { + if (newId) await initStores(); else resetStores() + }, { flush: 'sync' }) + return { + init, + reset, + isInit, projects, currentProjectId, + startProjectId, projectById, - addProject, - updateProject, - archiveProject, - deleteProject, setCurrentProjectId, - getCurrentProject + setStartProjectId, + initStores, + resetStores } }) diff --git a/src/stores/settings.ts b/src/stores/settings.ts new file mode 100644 index 0000000..9453984 --- /dev/null +++ b/src/stores/settings.ts @@ -0,0 +1,155 @@ +import { defineStore } from 'pinia' +import { ref, computed, inject } from 'vue' +import { api } from 'boot/axios' +import { useI18n } from 'vue-i18n' +import { Lang } from 'quasar' +import type { WebApp } from '@twa-dev/types' + +interface AppSettings { + fontSize: number + locale: string +} + +const defaultFontSize = 16 +const minFontSize = 10 +const maxFontSize = 22 +const fontSizeStep = 2 + +const defaultSettings: AppSettings = { + fontSize: defaultFontSize, + locale: 'en-US' +} + +export const useSettingsStore = defineStore('settings', () => { + const { locale: i18nLocale } = useI18n() + + const settings = ref({ ...defaultSettings }) + const tg = inject('tg') + + const isInit = ref(false) + + const currentFontSize = computed(() => settings.value?.fontSize ?? defaultFontSize) + const canIncrease = computed(() => currentFontSize.value < maxFontSize) + const canDecrease = computed(() => currentFontSize.value > minFontSize) + + const supportLocale = [ + { value: 'en-US', label: 'English' }, + { value: 'ru-RU', label: 'Русский' } + ] + + const quasarLangMap: Record = { + 'en-US': 'en-US', + 'ru-RU': 'ru' + } + + const updateQuasarLang = async (locale: string) => { + const quasarLang = quasarLangMap[locale] || 'en-US' + + try { + const langModule = await import( + `../../node_modules/quasar/lang/${quasarLang}.js` + ) + Lang.set(langModule.default) + } catch (e) { + console.error('Quasar Error load locale:', quasarLang, e) + } + } + + + + const detectLocale = (): string => { + const localeMap = { + ru: 'ru-RU', + en: 'en-US' + } as const satisfies Record + + type LocaleCode = keyof typeof localeMap + + const normLocale = (locale?: string): string | undefined => { + if (!locale) return undefined + const code = locale.split('-')[0] as LocaleCode + return localeMap[code] ?? undefined + } + + const tgLang = tg?.initDataUnsafe?.user?.language_code + const normalizedTgLang = normLocale(tgLang) + + return normalizedTgLang ?? normLocale(navigator.language) ?? 'en-US' + } + + const updateCssVariable = () => { + document.documentElement.style.setProperty( + '--dynamic-font-size', + `${currentFontSize.value}px` + ) + } + + const applyLocale = async () => { + if (settings.value.locale && i18nLocale) { + i18nLocale.value = settings.value.locale + await updateQuasarLang(settings.value.locale) + } + } + + const updateLocale = async (newLocale: string) => { + if (i18nLocale) { + i18nLocale.value = newLocale + await updateQuasarLang(newLocale) + settings.value.locale = newLocale + await saveSettings() + } + } + + const saveSettings = async () => { + await api.put('/settings', { settings: settings.value }) + } + + const updateSettings = async (newSettings: Partial) => { + settings.value = { ...settings.value, ...newSettings } + updateCssVariable() + await applyLocale() + await saveSettings() + } + + const init = async () => { + try { + const response = await api.get('/settings') + settings.value = { + fontSize: response.data.data.settings.fontSize || defaultSettings.fontSize, + locale: response.data.data.settings.locale || detectLocale() + } + } catch { + settings.value.locale = detectLocale() + } + updateCssVariable() + await applyLocale() + isInit.value = true + } + + const clampFontSize = (size: number) => + Math.max(minFontSize, Math.min(size, maxFontSize)) + + const increaseFontSize = async () => { + const newSize = clampFontSize(currentFontSize.value + fontSizeStep) + await updateSettings({ fontSize: newSize }) + } + + const decreaseFontSize = async () => { + const newSize = clampFontSize(currentFontSize.value - fontSizeStep) + await updateSettings({ fontSize: newSize }) + } + + return { + settings, + supportLocale, + isInit, + currentFontSize, + canIncrease, + canDecrease, + init, + increaseFontSize, + decreaseFontSize, + updateSettings, + updateLocale + } +}) diff --git a/src/stores/tasks.ts b/src/stores/tasks.ts new file mode 100644 index 0000000..ba0efb6 --- /dev/null +++ b/src/stores/tasks.ts @@ -0,0 +1,78 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' +import { api } from 'boot/axios' +import { useProjectsStore } from 'stores/projects' +import type { Task, TaskParams } from 'types/Task' + +export const useTasksStore = defineStore('tasks', () => { + + const tasks = ref([]) + const isInit = ref(false) + + const projectsStore = useProjectsStore() + const currentProjectId = computed(() => projectsStore.currentProjectId) + + async function init () { + const response = await api.get('/project/' + currentProjectId.value + '/task') + const tasksAPI = response.data.data + tasks.value.push(...tasksAPI) + isInit.value = true + } + + function reset () { + tasks.value = [] + isInit.value = false + } + + async function add (taskData: TaskParams) { + const response = await api.post('/project/' + currentProjectId.value + '/task', taskData) + const newTaskAPI = response.data.data + tasks.value.push(newTaskAPI) + return newTaskAPI + } + + async function update (taskId: number, taskData: TaskParams) { + const response = await api.put('/project/' + currentProjectId.value + '/task/' + taskId, taskData) + const taskAPI = response.data.data + const idx = tasks.value.findIndex(item => item.id === taskAPI.id) + if (tasks.value[idx]) Object.assign(tasks.value[idx], taskAPI) + } + + async function updateObservers (taskId: number, observers: number[]) { + const response = await api.put('/project/' + currentProjectId.value + '/task/' + taskId + '/observer', observers) + const observersAPI = response.data.data + const idx = tasks.value.findIndex(item => item.id === taskId) + if (tasks.value[idx]) tasks.value[idx].participants = observersAPI + } + + async function attachFiles (taskId: number, files: number[]) { + const response = await api.put('/project/' + currentProjectId.value + '/task/' + taskId + '/attach', files) + const filesAPI = response.data.data + const idx = tasks.value.findIndex(item => item.id === taskId) + if (tasks.value[idx]) tasks.value[idx].files = filesAPI + } + + async function remove (taskId: number) { + const response = await api.delete('/project/' + currentProjectId.value + '/task/' + taskId) + const taskAPIid = response.data.data.id + const idx = tasks.value.findIndex(item => item.id === taskAPIid ) + tasks.value.splice(idx, 1) + } + + function taskById (id :number) { + return tasks.value.find(el => el.id === id) + } + + return { + tasks, + isInit, + init, + reset, + add, + update, + updateObservers, + attachFiles, + remove, + taskById + } +}) diff --git a/src/stores/textSize.ts b/src/stores/textSize.ts deleted file mode 100644 index 9a4ed0a..0000000 --- a/src/stores/textSize.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { api } from 'boot/axios' -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' - -interface FontSizeResponse { - fontSize: number -} - -interface FontSizeError { - message: string - code: number -} - -export const useTextSizeStore = defineStore('textSize', () => { - // State - const baseSize = ref(16) // Значение по умолчанию - const isLoading = ref(false) - const error = ref(null) - const isInitialized = ref(false) - - // Константы - const minFontSize = 12 - const maxFontSize = 20 - const fontSizeStep = 2 - - // Getters - const currentFontSize = computed(() => baseSize.value) - const canIncrease = computed(() => baseSize.value < maxFontSize) - const canDecrease = computed(() => baseSize.value > minFontSize) - - // Actions - const fetchFontSize = async () => { - try { - isLoading.value = true - const response = await api.get('customer/settings') - baseSize.value = clampFontSize(response.data.fontSize) - updateCssVariable() - } catch (err) { - handleError(err, 'Failed to fetch font size') - baseSize.value = 16 // Fallback к значению по умолчанию - throw err - } finally { - isLoading.value = false - } - } - - const updateFontSize = async (newSize: number) => { - try { - const validatedSize = clampFontSize(newSize) - - await api.put('customer/settings', { fontSize: validatedSize }) - - baseSize.value = validatedSize - updateCssVariable() - error.value = null - } catch (err) { - handleError(err, 'Failed to update font size') - throw err - } - } - - const increaseFontSize = async () => { - if (!canIncrease.value) return - await updateFontSize(baseSize.value + fontSizeStep) - } - - const decreaseFontSize = async () => { - if (!canDecrease.value) return - await updateFontSize(baseSize.value - fontSizeStep) - } - - // Helpers - const clampFontSize = (size: number): number => { - return Math.max(minFontSize, Math.min(size, maxFontSize)) - } - - const updateCssVariable = () => { - document.documentElement.style.setProperty( - '--dynamic-font-size', - `${baseSize.value}px` - ) - } - - const handleError = (err: unknown, defaultMessage: string) => { - const apiError = err as { response?: { data: { message: string; code: number } } } - error.value = { - message: apiError?.response?.data?.message || defaultMessage, - code: apiError?.response?.data?.code || 500 - } - console.error('FontSize Error:', error.value) - } - - // Инициализация при первом использовании - const initialize = async () => { - if (isInitialized.value) return - - try { - await fetchFontSize() - } catch { - // Оставляем значение по умолчанию - } finally { - isInitialized.value = true - } - } - - return { - baseSize, - currentFontSize, - minFontSize, - maxFontSize, - isLoading, - error, - canIncrease, - canDecrease, - fetchFontSize, - increaseFontSize, - decreaseFontSize, - updateFontSize, - initialize - } -}) \ No newline at end of file diff --git a/src/stores/users.ts b/src/stores/users.ts new file mode 100644 index 0000000..8529bcb --- /dev/null +++ b/src/stores/users.ts @@ -0,0 +1,54 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' +import { api } from 'boot/axios' +import { useProjectsStore } from 'stores/projects' +import type { User } from 'types/User' + +export const useUsersStore = defineStore('users', () => { + + const users = ref([]) + const isInit = ref(false) + + const projectsStore = useProjectsStore() + const currentProjectId = computed(() => projectsStore.currentProjectId) + + async function init () { + const response = await api.get('/project/' + currentProjectId.value + '/user') + const usersAPI = response.data.data + users.value.push(...usersAPI) + isInit.value = true + } + + async function reload () { + reset() + await api.get('/project/' + currentProjectId.value + '/user/reload') + await init() + } + + function reset () { + users.value = [] + isInit.value = false + } + + function userById (id: number) { + return users.value.find(el =>el.id === id) + } + + function userNameById (id: number) { + const user = userById(id) + return user?.fullname + || [user?.firstname, user?.lastname].filter(Boolean).join(' ').trim() + || user?.username + || '---' + } + + return { + users, + isInit, + init, + reset, + reload, + userById, + userNameById + } +}) diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index d9a16e5..0000000 --- a/src/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { WebApp } from "@twa-dev/types" - -declare global { - interface Window { - Telegram: { - WebApp: WebApp - } - } -} - -interface ProjectParams { - name: string - description?: string - logo?: string - logo_as_bg: boolean -} - -interface Project extends ProjectParams { - id: number - is_archive: boolean - chats: number - companies: number - persons: number -} - -interface Chat { - id: number - // project_id: number - name: string - description?: string - logo?: string - persons: number - owner_id: number -} - -interface CompanyParams { - name: string - description?: string - address?: string - site?: string - phone?: string - email?: string - logo?: string -} - -interface Company extends CompanyParams { - id: number - project_id: number -} - -export type { - Project, - ProjectParams, - Chat, - Company, - CompanyParams -} diff --git a/src/types/Chat.ts b/src/types/Chat.ts new file mode 100644 index 0000000..f738c48 --- /dev/null +++ b/src/types/Chat.ts @@ -0,0 +1,19 @@ +interface Chat { + id: number + project_id: number + name: string | null + description: string | null + telegram_id: number + logo: string | null + is_channel: boolean + bot_can_ban: boolean + owner_id?: number + user_count: number + last_update_time: number + invite_link: string + [key: string]: unknown +} + +export type { + Chat +} diff --git a/src/types/File.ts b/src/types/File.ts new file mode 100644 index 0000000..7dcf80e --- /dev/null +++ b/src/types/File.ts @@ -0,0 +1,23 @@ +interface File { + id: number + project_id: number + origin_chat_id: number + origin_message_id: number + chat_id: number + message_id: number + file_id: number + filename: string + mime: string + caption: string + size: number + published_by: number + published: number + parent_type: 0 | 1 | 2 + parent_id: number + backup_state?: number + [key: string]: unknown +} + +export type { + File +} diff --git a/src/types/Meeting.ts b/src/types/Meeting.ts new file mode 100644 index 0000000..7f1f8f8 --- /dev/null +++ b/src/types/Meeting.ts @@ -0,0 +1,22 @@ +interface MeetingParams { + name: string + description: string + place: string + meet_date: number + chat_attach: number | null + participants: number[] + files: number[] + is_cancel: boolean +} + +interface Meeting extends MeetingParams { + id: number + project_id: number + created_by: number + [key: string]: unknown +} + +export type { + Meeting, + MeetingParams +} diff --git a/src/types/Project.ts b/src/types/Project.ts new file mode 100644 index 0000000..0944162 --- /dev/null +++ b/src/types/Project.ts @@ -0,0 +1,19 @@ +interface ProjectParams { + name: string + description: string + logo: string + is_logo_bg: boolean +} + +interface Project extends ProjectParams { + id: number + is_archived: boolean + chat_count: number + user_count: number + [key: string]: unknown +} + +export type { + Project, + ProjectParams +} diff --git a/src/types/Task.ts b/src/types/Task.ts new file mode 100644 index 0000000..72a52ae --- /dev/null +++ b/src/types/Task.ts @@ -0,0 +1,26 @@ +interface TaskParams { + name: string + description: string + assigned_to: number + priority: 0 | 1 | 2 | 3 + status: 1 | 5 + time_spent?: number + create_date: number + plan_date: number +} + +interface Task extends TaskParams { + id: number + project_id: number + created_by: number + closed_by: number | null + observers: number[] + files: number[] + close_date: number + [key: string]: unknown +} + +export type { + Task, + TaskParams +} diff --git a/src/types/User.ts b/src/types/User.ts new file mode 100644 index 0000000..a9b6b71 --- /dev/null +++ b/src/types/User.ts @@ -0,0 +1,22 @@ +interface User { + id: number + project_id: number + telegram_id: number + firstname: string | null + lastname: string | null + username: string | null + photo: string | null + phone: string + company_id: number + fullname: string + role: string + department: string + email: string + is_blocked: boolean + is_leave: boolean + [key: string]: unknown +} + +export type { + User +} diff --git a/todo.txt b/todo.txt index e313d65..88169a8 100644 --- a/todo.txt +++ b/todo.txt @@ -1,83 +1,34 @@ -0. Общее: +0. Общая панель: +- Панель выбора проекта +- Страница настроек +- Должна быть настройка: все проекты -1. Login: -+ Окно "Забыли пароль?" -+ Надпись "Неправильный логин или пароль" -+ Окно "Регистрация нового пользователя" -+ Верификация поля ввода e-mail (не делать - плохо выглядит) +1. Панель задач: +- Панель выбора задачи: +-- Фильтры +-- Выбор даты +- Панель конкретной задачи +- Панель добавить задачу +- Настройка Store Tasks -2. Account: -+ Работа с изображением логотипа компании -- Перенос аккаунта с телеграмм на логин/пароль -- Форма оплаты +2. Панель совещаний: +- Панель выбора совещаний: +-- Выбор даты +- Панель конкретного совещания +- Настройка Store Meetings -3. ProjectsPage: -+ Архивные проекты -+ (баг) Промотка шапки в конце прокрутки списка проектов -+ Добавить тень при прокрутке списка на заголовке "Проекты" -+ Окно добавить проект -+ При добавлении проекта проверять валидность, если не валидно то скрывать галку "Применить" +3. Панель файлов: +- Панель выбора файлов: +-- Фильтры +- Настройка Store Files -4.1 ProjectPage - Заголовок: -+ Анимация расширенной версии (плавное увеличение блока div) -+ Окно редактирования проекта -+ При изменении свойств проекта проверять валидность, если не валидно то скрывать галку "Применить" -+ Продумать backup (потом) -+ Окно отправки проекта в архив -+ Окно удаления проекта +4. Панель Контакты: +- Панель выбора контактов +- Панель конкретного контакта +- Настройка Store Users -4.2 ProjectPage - Чаты: -+ Окно прикрепления нового чата -+ Добавить диалог при слайдинге чата об подтверждении удаления и предупреждением. -+ Сделать стор с чатами -+ Настроить роутинг -+ У чатов добавить кол-во пользователей -- У чатов добавить указание владельца чата и его компанию -- Удаление чата свайпом и отключенные чаты. +5. Панель чаты: +- Панель выбора чата +- Настройка Store Chats -4.3 ProjectPage - Люди: -- Перечень сотрудников -+ Окно редактирования сотрудника -- При изменении сотрудников проверять валидность, если не валидно то скрывать галку "Применить" -- Сделать стор с персоналом -- Настроить роутинг -4.4 ProjectPage - Компании: -+ Перечень компаний -+ Окно редактирования компании -- При изменении компании проверять валидность, если не валидно то скрывать галку "Применить" -- Окно настройки видимости компаний - -4.5 ProjectPage - Маскировка: -- Сделать стор и настроить компоненты - -5. Settings: -- Роутинг -- Переключатель языков -+ Встроить в Телеграмм - -6. Лицензионное соглашение: -- Роутинг и заготовка -- Текст соглашения -- Встроить в Телеграмм - -BUGS: -+- 1. Прыгает кнопка fab при перещелкивании табов (при быстром переключении все равно прыгает, проблема установлена в q-page-sticky -как-то некорректно отрабатывается bottom и right) -+ 2. Верстка в шапке Projects плохая - переделать -- 3. Не хватает перевода местами -+ 4. При нажатии Back браузера скидывается активная табка. -+ 5. Криво работает удаление чата (полоски-бордюры) // дописывается стиль - -Need refactor -- 1. Слияение объектов разных типов, но с одинаковыми ключами (например, в updateProject через ObjectAssign) - -Current ToDo: -+ 1. pinia -+ 2. Реализовать функционал меню - редактирование проекта. (Бекап на потом) -+ 3. Архивные чаты и проекты. (Чаты отказался) -+4. Добавление компании. -+ 5. Удаление компании (слайдер), как в чате. -- 6. Страница аккаунта: -- 6.1 Переделать выбор платежей. -- 6.2 Окошко смены емейл аккаунта при входе с емейла. -- 7. Настроить git diff --git a/tsconfig.json b/tsconfig.json index 96fc57b..c77a8cc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,6 @@ { "extends": "./.quasar/tsconfig.json", "compilerOptions": { - "baseUrl": ".", - "paths": { - "src/*": ["./src/*"], - "app/*": ["./src/*"], - "components/*": ["./src/components/*"], - "layouts/*": ["./src/layouts/*"], - "pages/*": ["./src/pages/*"], - "assets/*": ["./src/assets/*"], - "boot/*": ["./src/boot/*"], - "stores/*": ["./src/stores/*"] - }, "types": ["@twa-dev/types", "node"] }, "include": ["src/**/*", "types/**/*"]