From cd818d3356d5738a9fb534e056d2e1055b2016ce Mon Sep 17 00:00:00 2001
From: Nikita Kiselev
Date: Mon, 3 Nov 2025 09:20:28 +0300
Subject: [PATCH] feat: update admin page
---
frontend/admin/bun.lock | 73 ++
frontend/admin/package.json | 5 +
frontend/admin/src/App.vue | 75 +-
frontend/admin/src/assets/main.css | 20 +
.../admin/src/components/OcImagePicker.vue | 6 +-
.../src/components/Settings/ItemBool.vue | 27 +
.../Settings/ItemCategoriesSelect.vue | 116 +++
.../src/components/Settings/ItemImage.vue | 26 +
.../src/components/Settings/ItemInput.vue | 45 ++
.../Settings/ItemProductsSelect.vue | 116 +++
.../src/components/Settings/ItemSelect.vue | 34 +
.../src/components/Settings/ItemTextarea.vue | 41 ++
.../components/Settings/ItemTgBotToken.vue | 145 ++++
.../src/components/Settings/ItemTgChatID.vue | 173 +++++
.../Settings/ItemTgMessageTemplate.vue | 169 +++++
.../components/Settings/ItemTgMiniAppLink.vue | 31 +
.../admin/src/components/Slider/Slider.vue | 6 +-
frontend/admin/src/components/Switcher.vue | 19 +-
frontend/admin/src/components/TopLead.vue | 112 +++
frontend/admin/src/main.js | 22 +-
frontend/admin/src/router/index.js | 26 +-
frontend/admin/src/stores/settings.js | 125 ++++
frontend/admin/src/stores/stats.js | 22 +
frontend/admin/src/utils/http.js | 142 ++++
frontend/admin/src/utils/toastHelper.js | 2 +
frontend/admin/src/views/GeneralView.vue | 56 ++
frontend/admin/src/views/HomeView.vue | 7 -
frontend/admin/src/views/MetricsView.vue | 29 +
frontend/admin/src/views/OrdersView.vue | 17 +
frontend/admin/src/views/SliderView.vue | 10 +
frontend/admin/src/views/StoreView.vue | 80 ++
frontend/admin/src/views/TelegramView.vue | 28 +
frontend/admin/src/views/TextsView.vue | 21 +
.../controller/extension/module/tgshop.php | 617 ++++------------
.../template/extension/module/tgshop.twig | 447 +-----------
.../extension/module/tgshop_init.twig | 60 --
.../controller/extension/tgshop/handle.php | 3 +-
.../bastion/ApplicationFactory.php | 10 +-
.../bastion/Handlers/AutocompleteHandler.php | 64 ++
.../bastion/Handlers/SettingsHandler.php | 45 +-
.../bastion/Handlers/StatsHandler.php | 48 ++
.../bastion/Handlers/TelegramHandler.php | 88 ++-
.../bastion/Services/BotTokenConfigurator.php | 13 +-
.../bastion/Services/SettingsService.php | 22 +
.../oc_telegram_shop/bastion/config.php | 90 ++-
.../oc_telegram_shop/bastion/routes.php | 9 +
.../framework/Application.php | 6 +-
.../framework/Config/Settings.php | 31 +-
.../framework/Config/SettingsInterface.php | 14 +
.../framework/Container/Container.php | 10 +-
.../framework/ErrorHandler.php | 8 +
.../Exceptions/ActionNotFoundException.php | 13 +
.../QueryBuilderServiceProvider.php | 12 +-
.../framework/Router/Router.php | 5 +-
.../framework/Support/Arr.php | 101 ++-
.../framework/Support/Str.php | 37 +
.../framework/Support/helpers.php | 2 +-
.../framework/Telegram/SignatureValidator.php | 11 +-
.../Telegram/TelegramServiceProvider.php | 5 +-
.../Translator/TranslatorServiceProvider.php | 2 +-
.../src/ApplicationFactory.php | 4 +-
.../src/DTO/Settings/AppDTO.php | 89 +++
.../src/DTO/Settings/ConfigDTO.php | 98 +++
.../src/DTO/Settings/DatabaseDTO.php | 71 ++
.../src/DTO/Settings/LogsDTO.php | 25 +
.../DTO/Settings/MainpageSlider/LinkDTO.php | 42 ++
.../DTO/Settings/MainpageSlider/LinkType.php | 10 +
.../Settings/MainpageSlider/LinkValueDTO.php | 55 ++
.../MainpageSlider/MainpageSliderDTO.php | 111 +++
.../DTO/Settings/MainpageSlider/SlideDTO.php | 45 ++
.../src/DTO/Settings/MetricsDTO.php | 36 +
.../src/DTO/Settings/OrdersDTO.php | 34 +
.../src/DTO/Settings/SlidersDTO.php | 28 +
.../src/DTO/Settings/StoreDTO.php | 120 +++
.../src/DTO/Settings/TelegramDTO.php | 63 ++
.../src/DTO/Settings/TextsDTO.php | 45 ++
.../src/Handlers/BannerHandler.php | 31 +-
.../src/Handlers/CategoriesHandler.php | 12 +-
.../src/Handlers/ProductsHandler.php | 8 +-
.../src/Handlers/SettingsHandler.php | 60 +-
.../src/Handlers/TelegramHandler.php | 2 +
.../SettingsServiceProvider.php | 21 +
.../src/Services/OrderCreateService.php | 20 +-
.../src/Services/ProductsService.php | 13 +-
.../Services/SettingsSerializerService.php | 686 ++++++++++++++++++
.../src/Services/SettingsService.php | 23 +
.../oc_telegram_shop/src/Support/Utils.php | 5 +
.../oc_telegram_shop/tests/TestCase.php | 3 +-
.../oc_telegram_shop/tests/Unit/ArrTest.php | 472 ++++++++++++
.../tests/Unit/CriteriaBuilderTest.php | 4 +-
.../tests/Unit/SettingsTest.php | 55 --
.../oc_telegram_shop/tests/Unit/StrTest.php | 55 ++
spa/.vite/deps/_metadata.json | 8 -
spa/.vite/deps/package.json | 3 -
94 files changed, 4729 insertions(+), 1227 deletions(-)
create mode 100644 frontend/admin/src/components/Settings/ItemBool.vue
create mode 100644 frontend/admin/src/components/Settings/ItemCategoriesSelect.vue
create mode 100644 frontend/admin/src/components/Settings/ItemImage.vue
create mode 100644 frontend/admin/src/components/Settings/ItemInput.vue
create mode 100644 frontend/admin/src/components/Settings/ItemProductsSelect.vue
create mode 100644 frontend/admin/src/components/Settings/ItemSelect.vue
create mode 100644 frontend/admin/src/components/Settings/ItemTextarea.vue
create mode 100644 frontend/admin/src/components/Settings/ItemTgBotToken.vue
create mode 100644 frontend/admin/src/components/Settings/ItemTgChatID.vue
create mode 100644 frontend/admin/src/components/Settings/ItemTgMessageTemplate.vue
create mode 100644 frontend/admin/src/components/Settings/ItemTgMiniAppLink.vue
create mode 100644 frontend/admin/src/components/TopLead.vue
create mode 100644 frontend/admin/src/stores/settings.js
create mode 100644 frontend/admin/src/stores/stats.js
create mode 100644 frontend/admin/src/utils/http.js
create mode 100644 frontend/admin/src/utils/toastHelper.js
create mode 100644 frontend/admin/src/views/GeneralView.vue
delete mode 100644 frontend/admin/src/views/HomeView.vue
create mode 100644 frontend/admin/src/views/MetricsView.vue
create mode 100644 frontend/admin/src/views/OrdersView.vue
create mode 100644 frontend/admin/src/views/SliderView.vue
create mode 100644 frontend/admin/src/views/StoreView.vue
create mode 100644 frontend/admin/src/views/TelegramView.vue
create mode 100644 frontend/admin/src/views/TextsView.vue
delete mode 100755 module/oc_telegram_shop/upload/admin/view/template/extension/module/tgshop_init.twig
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/AutocompleteHandler.php
create mode 100644 module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/StatsHandler.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Services/SettingsService.php
create mode 100644 module/oc_telegram_shop/upload/oc_telegram_shop/framework/Config/SettingsInterface.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/framework/Support/Str.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/AppDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/ConfigDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/DatabaseDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/LogsDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/MainpageSlider/LinkDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/MainpageSlider/LinkType.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/MainpageSlider/LinkValueDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/MainpageSlider/MainpageSliderDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/MainpageSlider/SlideDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/MetricsDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/OrdersDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/SlidersDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/StoreDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/TelegramDTO.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/DTO/Settings/TextsDTO.php
create mode 100644 module/oc_telegram_shop/upload/oc_telegram_shop/src/ServiceProviders/SettingsServiceProvider.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/SettingsSerializerService.php
create mode 100644 module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/SettingsService.php
create mode 100755 module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/StrTest.php
delete mode 100644 spa/.vite/deps/_metadata.json
delete mode 100644 spa/.vite/deps/package.json
diff --git a/frontend/admin/bun.lock b/frontend/admin/bun.lock
index 93f894e..ed406be 100644
--- a/frontend/admin/bun.lock
+++ b/frontend/admin/bun.lock
@@ -4,9 +4,14 @@
"": {
"name": "admin",
"dependencies": {
+ "@primeuix/themes": "^1.2.5",
"@tailwindcss/vite": "^4.1.16",
+ "@vueuse/core": "^14.0.0",
+ "axios": "^1.13.1",
"daisyui": "^5.4.2",
+ "mitt": "^3.0.1",
"pinia": "^3.0.3",
+ "primevue": "^4.4.1",
"tailwindcss": "^4.1.16",
"vue": "^3.5.22",
"vue-router": "^4.6.3",
@@ -239,6 +244,18 @@
"@prettier/plugin-oxc": ["@prettier/plugin-oxc@0.0.4", "", { "dependencies": { "oxc-parser": "0.74.0" } }, "sha512-UGXe+g/rSRbglL0FOJiar+a+nUrst7KaFmsg05wYbKiInGWP6eAj/f8A2Uobgo5KxEtb2X10zeflNH6RK2xeIQ=="],
+ "@primeuix/styled": ["@primeuix/styled@0.7.4", "", { "dependencies": { "@primeuix/utils": "^0.6.1" } }, "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ=="],
+
+ "@primeuix/styles": ["@primeuix/styles@1.2.5", "", { "dependencies": { "@primeuix/styled": "^0.7.3" } }, "sha512-nypFRct/oaaBZqP4jinT0puW8ZIfs4u+l/vqUFmJEPU332fl5ePj6DoOpQgTLzo3OfmvSmz5a5/5b4OJJmmi7Q=="],
+
+ "@primeuix/themes": ["@primeuix/themes@1.2.5", "", { "dependencies": { "@primeuix/styled": "^0.7.3" } }, "sha512-n3YkwJrHQaEESc/D/A/iD815sxp8cKnmzscA6a8Tm8YvMtYU32eCahwLLe6h5rywghVwxASWuG36XBgISYOIjQ=="],
+
+ "@primeuix/utils": ["@primeuix/utils@0.6.2", "", {}, "sha512-l+li4z6UAwamqbu4xodN3lqr1G4S37DKEJ7Y/nE3MjEx7zJ+KIBkb5voF26Nuly1UCmIcRWQJokhLRF7+sRhyg=="],
+
+ "@primevue/core": ["@primevue/core@4.4.1", "", { "dependencies": { "@primeuix/styled": "^0.7.4", "@primeuix/utils": "^0.6.1" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-RG56iDKIJT//EtntjQzOiWOHZZJczw/qWWtdL5vFvw8/QDS9DPKn8HLpXK7N5Le6KK1MLXUsxoiGTZK+poUFUg=="],
+
+ "@primevue/icons": ["@primevue/icons@4.4.1", "", { "dependencies": { "@primeuix/utils": "^0.6.1", "@primevue/core": "4.4.1" } }, "sha512-UfDimrIjVdY6EziwieyV4zPKzW6mnKHKhy4Dgyjv2oI6pNeuim+onbJo1ce22PEGXW78vfblG/3/JIzVHFweqQ=="],
+
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.29", "", {}, "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.5", "", { "os": "android", "cpu": "arm" }, "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ=="],
@@ -321,6 +338,8 @@
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
+ "@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
+
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.29" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", "vue": "^3.2.25" } }, "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw=="],
"@vue/babel-helper-vue-transform-on": ["@vue/babel-helper-vue-transform-on@1.5.0", "", {}, "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA=="],
@@ -357,6 +376,12 @@
"@vue/shared": ["@vue/shared@3.5.22", "", {}, "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w=="],
+ "@vueuse/core": ["@vueuse/core@14.0.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.0.0", "@vueuse/shared": "14.0.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-d6tKRWkZE8IQElX2aHBxXOMD478fHIYV+Dzm2y9Ag122ICBpNKtGICiXKOhWU3L1kKdttDD9dCMS4bGP3jhCTQ=="],
+
+ "@vueuse/metadata": ["@vueuse/metadata@14.0.0", "", {}, "sha512-6yoGqbJcMldVCevkFiHDBTB1V5Hq+G/haPlGIuaFZHpXC0HADB0EN1ryQAAceiW+ryS3niUwvdFbGiqHqBrfVA=="],
+
+ "@vueuse/shared": ["@vueuse/shared@14.0.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-mTCA0uczBgurRlwVaQHfG0Ja7UdGe4g9mwffiJmvLiTtp1G4AQyIjej6si/k8c8pUwTfVpNufck+23gXptPAkw=="],
+
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
@@ -369,6 +394,10 @@
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
+
+ "axios": ["axios@1.13.1", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw=="],
+
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.21", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q=="],
@@ -383,6 +412,8 @@
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
+
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001751", "", {}, "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw=="],
@@ -393,6 +424,8 @@
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+ "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
+
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
@@ -417,8 +450,12 @@
"define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
+ "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
+
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
+
"electron-to-chromium": ["electron-to-chromium@1.5.243", "", {}, "sha512-ZCphxFW3Q1TVhcgS9blfut1PX8lusVi2SvXQgmEEnK4TCmE1JhH2JkjJN+DNt0pJJwfBri5AROBnz2b/C+YU9g=="],
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
@@ -427,6 +464,14 @@
"error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
+
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
+
+ "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
+
+ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
+
"esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
@@ -477,18 +522,36 @@
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
+ "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
+
+ "form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
+
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
+
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
+
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
+
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
+
+ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
+
+ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
+
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
@@ -569,8 +632,14 @@
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
+
"memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="],
+ "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
+ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
@@ -633,6 +702,10 @@
"prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="],
+ "primevue": ["primevue@4.4.1", "", { "dependencies": { "@primeuix/styled": "^0.7.4", "@primeuix/styles": "^1.2.5", "@primeuix/utils": "^0.6.1", "@primevue/core": "4.4.1", "@primevue/icons": "4.4.1" } }, "sha512-JbHBa5k30pZ7mn/z4vYBOnyt5GrR15eM3X0wa3VanonxnFLYkTEx8OMh33aU6ndWeOfi7Ef57dOL3bTH+3f4hQ=="],
+
+ "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
+
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="],
diff --git a/frontend/admin/package.json b/frontend/admin/package.json
index 631bc46..f953149 100644
--- a/frontend/admin/package.json
+++ b/frontend/admin/package.json
@@ -16,9 +16,14 @@
"format": "prettier --write src/"
},
"dependencies": {
+ "@primeuix/themes": "^1.2.5",
"@tailwindcss/vite": "^4.1.16",
+ "@vueuse/core": "^14.0.0",
+ "axios": "^1.13.1",
"daisyui": "^5.4.2",
+ "mitt": "^3.0.1",
"pinia": "^3.0.3",
+ "primevue": "^4.4.1",
"tailwindcss": "^4.1.16",
"vue": "^3.5.22",
"vue-router": "^4.6.3"
diff --git a/frontend/admin/src/App.vue b/frontend/admin/src/App.vue
index 65662c0..c02445b 100644
--- a/frontend/admin/src/App.vue
+++ b/frontend/admin/src/App.vue
@@ -1,10 +1,77 @@
+
+
+
+
+
+ Общие
+
+
+
+ Telegram
+
+
+
+ Метрики
+
+
+
+ Магазин
+
+
+
+ Тексты
+
+
+
+ Заказы
+
+
+
+ Слайдер
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ settings.error }}
+
Обратитесь в поддержку
+
+
+
+
-
-
-
diff --git a/frontend/admin/src/components/Settings/ItemCategoriesSelect.vue b/frontend/admin/src/components/Settings/ItemCategoriesSelect.vue
new file mode 100644
index 0000000..f456b6f
--- /dev/null
+++ b/frontend/admin/src/components/Settings/ItemCategoriesSelect.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+ Загрузка списка категорий...
+
+
+
+
+
+
{{ product.name }}
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/admin/src/components/Settings/ItemImage.vue b/frontend/admin/src/components/Settings/ItemImage.vue
new file mode 100644
index 0000000..c7478d9
--- /dev/null
+++ b/frontend/admin/src/components/Settings/ItemImage.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/admin/src/components/Settings/ItemInput.vue b/frontend/admin/src/components/Settings/ItemInput.vue
new file mode 100644
index 0000000..f342fa0
--- /dev/null
+++ b/frontend/admin/src/components/Settings/ItemInput.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/admin/src/components/Settings/ItemProductsSelect.vue b/frontend/admin/src/components/Settings/ItemProductsSelect.vue
new file mode 100644
index 0000000..ef172ec
--- /dev/null
+++ b/frontend/admin/src/components/Settings/ItemProductsSelect.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+ Загрузка списка товаров...
+
+
+
+
+
+
{{ product.name }}
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/admin/src/components/Settings/ItemSelect.vue b/frontend/admin/src/components/Settings/ItemSelect.vue
new file mode 100644
index 0000000..ee20698
--- /dev/null
+++ b/frontend/admin/src/components/Settings/ItemSelect.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
+ {{ value }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/admin/src/components/Settings/ItemTextarea.vue b/frontend/admin/src/components/Settings/ItemTextarea.vue
new file mode 100644
index 0000000..72b7dc9
--- /dev/null
+++ b/frontend/admin/src/components/Settings/ItemTextarea.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/admin/src/components/Settings/ItemTgBotToken.vue b/frontend/admin/src/components/Settings/ItemTgBotToken.vue
new file mode 100644
index 0000000..4929478
--- /dev/null
+++ b/frontend/admin/src/components/Settings/ItemTgBotToken.vue
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+ {{ isLoading ? 'Проверяю...' : 'Проверить Bot Token' }}
+
+
+
+
+
+
+ {{ validationStatus }}
+
+
+
+
+ Подробная инструкция доступна в
+ документации
+
+ .
+
+
+
+
+
+
+
diff --git a/frontend/admin/src/components/Settings/ItemTgChatID.vue b/frontend/admin/src/components/Settings/ItemTgChatID.vue
new file mode 100644
index 0000000..4e7cc06
--- /dev/null
+++ b/frontend/admin/src/components/Settings/ItemTgChatID.vue
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+ {{ isLoading ? 'Получаю...' : 'Получить Chat ID' }}
+
+
+
+
+
+
+ {{ statusMessage }}
+
+
+
+ Инструкция как получить ChatID.
+
+
+
+
Как получить Chat ID
+
+ Убедитесь, что Telegram Bot Token введён выше.
+ Откройте вашего бота в Telegram и отправьте ему кодовое слово: opencart_get_chatid. Важно отправить именно такое сообщение, иначе не сработает.
+ Вернитесь сюда и нажмите кнопку «Получить Chat ID» — скрипт автоматически подставит его в поле ниже.
+
+
+
+
+
+
+ BotToken не указан. Пожалуйста, введите корректный BotToken. После этого здесь станет доступна настройка ChatID.
+
+
+
+
+ Идентификатор Telegram-чата, куда будут отправляться уведомления о новых заказах. Если оставить поле пустым, уведомления отправляться не будут.
+
+
+
+
+
+
+
+
diff --git a/frontend/admin/src/components/Settings/ItemTgMessageTemplate.vue b/frontend/admin/src/components/Settings/ItemTgMessageTemplate.vue
new file mode 100644
index 0000000..a22c8a5
--- /dev/null
+++ b/frontend/admin/src/components/Settings/ItemTgMessageTemplate.vue
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+ Документация
+
+
+
+ {{ isSending ? 'Отправляю...' : 'Отправить тестовое уведомление' }}
+
+
+
+
+
+
Вы можете использовать переменные:
+
+ {store_name} — название магазина
+ {order_id} — номер заказа
+ {customer} — имя и фамилия покупателя
+ {email} — email покупателя
+ {phone} — телефон
+ {comment} — комментарий к заказу
+ {address} — адрес доставки
+ {total} — сумма заказа
+ {ip} — IP покупателя
+ {created_at} — дата и время создания заказа
+
+
+ Форматирование: поддерживается
+
+ *MarkdownV2*
+
+ .
+
+
Символы, которые нужно экранировать в тексте:
+
_ * [ ] ( ) ~ ` > # + - = | { } . !
+
+ Каждый из них нужно экранировать обратным слэшем \, если он не используется для форматирования.
+ Например вместо Заказ #123 нужно писать Заказ \#123.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/admin/src/components/Settings/ItemTgMiniAppLink.vue b/frontend/admin/src/components/Settings/ItemTgMiniAppLink.vue
new file mode 100644
index 0000000..f239fe7
--- /dev/null
+++ b/frontend/admin/src/components/Settings/ItemTgMiniAppLink.vue
@@ -0,0 +1,31 @@
+
+
+ Токен, полученный при создании бота через @BotFather.
+ Он используется для взаимодействия модуля с Telegram API.
+ Подробная инструкция доступна в
+
+ документации
+ .
+
+
+
+
+
+
diff --git a/frontend/admin/src/components/Slider/Slider.vue b/frontend/admin/src/components/Slider/Slider.vue
index 7c382e7..6b178a8 100644
--- a/frontend/admin/src/components/Slider/Slider.vue
+++ b/frontend/admin/src/components/Slider/Slider.vue
@@ -157,7 +157,7 @@ import LinkSelector from "@/components/Slider/LinkSelector.vue";
import SettingsItem from "@/components/SettingsItem.vue";
import Switcher from "@/components/Switcher.vue";
-const slider = ref({});
+const slider = defineModel();
function removeSlide(index) {
slider.value.slides.splice(index, 1);
@@ -173,10 +173,6 @@ function addSlide() {
image: '',
});
}
-
-onMounted(() => {
- slider.value = JSON.parse(window.TeleCart.mainpage_slider);
-});
diff --git a/frontend/admin/src/main.js b/frontend/admin/src/main.js
index c83f111..4097131 100644
--- a/frontend/admin/src/main.js
+++ b/frontend/admin/src/main.js
@@ -3,6 +3,15 @@ import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
+import {useSettingsStore} from "@/stores/settings.js";
+import PrimeVue from 'primevue/config';
+import Aura from '@primeuix/themes/aura';
+import ToastService from 'primevue/toastservice';
+import {definePreset} from "@primeuix/themes";
+
+const MyPreset = definePreset(Aura, {
+
+});
function onReady(fn) {
if (document.readyState === 'loading') {
@@ -12,9 +21,20 @@ function onReady(fn) {
}
}
-onReady(() => {
+onReady(async () => {
const app = createApp(App);
app.use(createPinia());
app.use(router);
+ app.use(PrimeVue, {
+ theme: {
+ preset: MyPreset,
+ options: {
+ cssLayer: false, // если используешь Tailwind, отключает layering
+ },
+ }
+ });
+ app.use(ToastService);
+
app.mount('#app');
+ await useSettingsStore().fetchSettings();
});
diff --git a/frontend/admin/src/router/index.js b/frontend/admin/src/router/index.js
index 4569e2f..5bbba6a 100644
--- a/frontend/admin/src/router/index.js
+++ b/frontend/admin/src/router/index.js
@@ -1,15 +1,23 @@
-import {createMemoryHistory, createRouter} from 'vue-router'
-import HomeView from '../views/HomeView.vue'
+import {createMemoryHistory, createRouter} from 'vue-router';
+import SliderView from "@/views/SliderView.vue";
+import GeneralView from "@/views/GeneralView.vue";
+import TextsView from "@/views/TextsView.vue";
+import OrdersView from "@/views/OrdersView.vue";
+import TelegramView from "@/views/TelegramView.vue";
+import MetricsView from "@/views/MetricsView.vue";
+import StoreView from "@/views/StoreView.vue";
const router = createRouter({
history: createMemoryHistory(),
routes: [
- {
- path: '/',
- name: 'home',
- component: HomeView,
- },
+ {path: '/', name: 'general', component: GeneralView},
+ {path: '/slider', name: 'slider', component: SliderView},
+ {path: '/orders', name: 'orders', component: OrdersView},
+ {path: '/texts', name: 'texts', component: TextsView},
+ {path: '/telegram', name: 'telegram', component: TelegramView},
+ {path: '/metrics', name: 'metrics', component: MetricsView},
+ {path: '/store', name: 'store', component: StoreView},
],
-})
+});
-export default router
+export default router;
diff --git a/frontend/admin/src/stores/settings.js b/frontend/admin/src/stores/settings.js
new file mode 100644
index 0000000..1c77814
--- /dev/null
+++ b/frontend/admin/src/stores/settings.js
@@ -0,0 +1,125 @@
+import {defineStore} from "pinia";
+import {apiGet, apiPost} from "@/utils/http.js";
+import {toastBus} from "@/utils/toastHelper.js";
+
+export const useSettingsStore = defineStore('settings', {
+ state: () => ({
+ isLoading: false,
+ error: null,
+
+ items: {
+ app: {
+ app_enabled: true,
+ app_name: '',
+ app_icon: null,
+ theme_light: 'light',
+ theme_dark: 'dark',
+ app_debug: false,
+ },
+
+ telegram: {
+ mini_app_url: '',
+ bot_token: '',
+ chat_id: '',
+ owner_notification_template: '',
+ customer_notification_template: '',
+ },
+
+ metrics: {
+ yandex_metrika_enabled: false,
+ yandex_metrika_counter: '',
+ },
+
+ store: {
+ enable_store: true,
+ mainpage_products: 'most_viewed',
+ featured_products: [],
+ mainpage_categories: 'latest10',
+ featured_categories: [],
+ feature_coupons: true,
+ feature_vouchers: true,
+ },
+
+ orders: {
+ order_default_status_id: 1,
+ },
+
+ texts: {
+ text_no_more_products: '',
+ text_empty_cart: '',
+ text_order_created_success: '',
+ },
+
+ sliders: {
+ mainpage_slider: {
+ is_enabled: false,
+ effect: "slide",
+ pagination: true,
+ scrollbar: false,
+ free_mode: false,
+ space_between: 30,
+ autoplay: false,
+ loop: false,
+ slides: [],
+ },
+ },
+ },
+ }),
+
+ getters: {
+ app_icon_preview: (state) => {
+ if (!state.items.app.app_icon) return '/image/cache/no_image-100x100.png';
+ const extIndex = state.items.app.app_icon.lastIndexOf('.');
+ const ext = state.items.app.app_icon.substring(extIndex);
+ const filename = state.items.app.app_icon.substring(0, extIndex);
+ return `/image/cache/${filename}-100x100${ext}`;
+ },
+ },
+
+ actions: {
+ async fetchSettings() {
+ this.isLoading = true;
+ this.error = null;
+ const response = await apiGet('getSettingsForm');
+ if (response.success) {
+ this.items = {
+ ...this.items,
+ ...response.data,
+ };
+ } else {
+ this.error = 'Возникли проблемы при загрузке настроек.';
+ }
+ this.isLoading = false;
+ },
+
+ async saveSettings() {
+ this.isLoading = true;
+ const settings = this.transformSettingsToStore(this.items);
+ const response = await apiPost('saveSettingsForm', settings);
+
+ if (response.success === true) {
+ toastBus.emit('show', {
+ severity: 'success',
+ summary: 'Готово!',
+ detail: 'Настройки сохранены.',
+ life: 2000,
+ });
+ } else {
+ toastBus.emit('show', {
+ severity: 'error',
+ summary: 'Ошибка',
+ detail: 'Возникли проблемы при сохранении настроек на сервере.',
+ life: 2000,
+ });
+ }
+
+
+
+ this.isLoading = false;
+ },
+
+ transformSettingsToStore(items) {
+ return items;
+ },
+ },
+});
diff --git a/frontend/admin/src/stores/stats.js b/frontend/admin/src/stores/stats.js
new file mode 100644
index 0000000..2afac9a
--- /dev/null
+++ b/frontend/admin/src/stores/stats.js
@@ -0,0 +1,22 @@
+import {defineStore} from "pinia";
+import {apiGet, apiPost} from "@/utils/http.js";
+
+export const useStatsStore = defineStore('stats', {
+ state: () => ({
+ items: {
+ orders_count: null,
+ orders_total_amount: null,
+ order_products_count: null,
+ }
+ }),
+
+ actions: {
+ async fetchStats() {
+ const response = await apiPost('getDashboardStats');
+ this.items.orders_count = response.data?.data?.orders_count;
+ this.items.orders_total_amount = response.data?.data?.orders_total_amount;
+ this.items.order_products_count = response.data?.data?.order_products_count;
+ }
+ },
+
+});
diff --git a/frontend/admin/src/utils/http.js b/frontend/admin/src/utils/http.js
new file mode 100644
index 0000000..d68a0e1
--- /dev/null
+++ b/frontend/admin/src/utils/http.js
@@ -0,0 +1,142 @@
+import axios from 'axios';
+
+/**
+ * Получает user_token из глобального объекта TeleCart
+ */
+function getUserToken() {
+ if (typeof window !== 'undefined' && window.TeleCart?.user_token) {
+ return window.TeleCart.user_token;
+ }
+
+ // Fallback: пытаемся получить из URL как запасной вариант
+ if (typeof window !== 'undefined') {
+ const urlParams = new URLSearchParams(window.location.search);
+ return urlParams.get('user_token') || '';
+ }
+
+ return '';
+}
+
+/**
+ * Базовый URL для API запросов
+ */
+function getBaseUrl() {
+ return '/admin/index.php';
+}
+
+/**
+ * Создает URL для API запроса
+ * @param {string} apiAction - действие API (например, 'configureBotToken')
+ * @returns {string} полный URL
+ */
+function buildApiUrl(apiAction) {
+ const baseUrl = getBaseUrl();
+ const userToken = getUserToken();
+ return `${baseUrl}?route=extension/module/tgshop/handle&api_action=${apiAction}&user_token=${userToken}`;
+}
+
+/**
+ * HTTP клиент для работы с API
+ */
+const httpClient = axios.create({
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+});
+
+/**
+ * Выполняет POST запрос к API
+ * @param {string} apiAction - действие API
+ * @param {object} data - данные для отправки
+ * @returns {Promise} результат запроса
+ */
+export async function apiPost(apiAction, data = {}) {
+ const url = buildApiUrl(apiAction);
+
+ try {
+ const response = await httpClient.post(url, data);
+ return {
+ success: true,
+ data: response.data,
+ status: response.status,
+ };
+ } catch (error) {
+ // Обработка ошибок axios
+ if (error.response) {
+ // Сервер вернул ошибку
+ const status = error.response.status;
+ const errorData = error.response.data;
+
+ return {
+ success: false,
+ error: errorData?.error || error.response.statusText,
+ status,
+ data: errorData,
+ };
+ } else if (error.request) {
+ // Запрос был отправлен, но ответа не получено
+ return {
+ success: false,
+ error: 'Не удалось получить ответ от сервера',
+ status: 0,
+ };
+ } else {
+ // Ошибка при настройке запроса
+ return {
+ success: false,
+ error: error.message || 'Произошла неизвестная ошибка',
+ status: 0,
+ };
+ }
+ }
+}
+
+/**
+ * Выполняет GET запрос к API
+ * @param {string} apiAction - действие API
+ * @param {object} params - query параметры
+ * @returns {Promise} результат запроса
+ */
+export async function apiGet(apiAction, params = {}) {
+ const url = buildApiUrl(apiAction);
+
+ try {
+ const response = await httpClient.get(url, { params: params });
+ return {
+ success: true,
+ data: response.data.data,
+ status: response.status,
+ };
+ } catch (error) {
+ if (error.response) {
+ const status = error.response.status;
+ const errorData = error.response.data;
+
+ return {
+ success: false,
+ error: errorData?.error || error.response.statusText,
+ status,
+ data: errorData,
+ };
+ } else if (error.request) {
+ return {
+ success: false,
+ error: 'Не удалось получить ответ от сервера',
+ status: 0,
+ };
+ } else {
+ return {
+ success: false,
+ error: error.message || 'Произошла неизвестная ошибка',
+ status: 0,
+ };
+ }
+ }
+}
+
+export default {
+ apiPost,
+ apiGet,
+ getUserToken,
+};
+
diff --git a/frontend/admin/src/utils/toastHelper.js b/frontend/admin/src/utils/toastHelper.js
new file mode 100644
index 0000000..96ca792
--- /dev/null
+++ b/frontend/admin/src/utils/toastHelper.js
@@ -0,0 +1,2 @@
+import mitt from 'mitt';
+export const toastBus = mitt();
diff --git a/frontend/admin/src/views/GeneralView.vue b/frontend/admin/src/views/GeneralView.vue
new file mode 100644
index 0000000..93af1c3
--- /dev/null
+++ b/frontend/admin/src/views/GeneralView.vue
@@ -0,0 +1,56 @@
+
+
+ Если выключено, покупатели в Telegram увидят сообщение, что магазин временно закрыт.
+ Заказы и просмотр товаров будут недоступны.
+
+
+
+ Отображается в заголовке Telegram Mini App при запуске, а также используется как подпись
+ под иконкой, если пользователь добавит приложение на главный экран своего устройства.
+ Рекомендуется короткое и понятное название (до 20 символов).
+ Если оставить пустым, то название выводиться не будет.
+
+
+
+ Изображение, которое будет отображаться в Telegram Mini App.
+
+
+
+ Выберите стиль, который будет использоваться при отображении вашего магазина
+ в Telegram для дневного режима.
+
+ Посмотреть как выглядят темы
+
+
+
+
+ Выберите стиль, который будет использоваться при отображении вашего магазина
+ в Telegram для ночного режима.
+
+ Посмотреть как выглядят темы
+
+
+
+
+ Режим разработчика. Рекомендуется включать только по необходимости.
+ В остальных случаях, для нормальной работы магазина, должен быть выключен.
+
+
+
+
+
+
diff --git a/frontend/admin/src/views/HomeView.vue b/frontend/admin/src/views/HomeView.vue
deleted file mode 100644
index 9000062..0000000
--- a/frontend/admin/src/views/HomeView.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/frontend/admin/src/views/MetricsView.vue b/frontend/admin/src/views/MetricsView.vue
new file mode 100644
index 0000000..85dcc60
--- /dev/null
+++ b/frontend/admin/src/views/MetricsView.vue
@@ -0,0 +1,29 @@
+
+
+ Задействовать Яндекс.Метрику для Telegram магазина.
+
+
+
+ Код счётчика нужно предварительно настроить, чтобы он работал корректно с Telegram Mini App.
+
+ Инструкция как настроить i.fa.fa-external-link
+ .
+ Для проверки интеграции через кнопку "Проверить" в интерфейсе Яндекс Метрики,
+ необходимо сначала включить "Режим разработчика" на вкладке "Общие".
+
+
+
+
diff --git a/frontend/admin/src/views/OrdersView.vue b/frontend/admin/src/views/OrdersView.vue
new file mode 100644
index 0000000..b778039
--- /dev/null
+++ b/frontend/admin/src/views/OrdersView.vue
@@ -0,0 +1,17 @@
+
+
+ Статус, с которым будут создаваться заказы через Telegram по умолчанию.
+
+
+
+
diff --git a/frontend/admin/src/views/SliderView.vue b/frontend/admin/src/views/SliderView.vue
new file mode 100644
index 0000000..152fce9
--- /dev/null
+++ b/frontend/admin/src/views/SliderView.vue
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/frontend/admin/src/views/StoreView.vue b/frontend/admin/src/views/StoreView.vue
new file mode 100644
index 0000000..6e07e75
--- /dev/null
+++ b/frontend/admin/src/views/StoreView.vue
@@ -0,0 +1,80 @@
+
+
+ Если опция включена — пользователи смогут оформлять
+ заказы прямо в Telegram-магазине.
+ Если выключена — оформление заказов будет недоступно. Вместо кнопки «Добавить
+ в корзину» пользователи увидят кнопку «Перейти к товару», которая откроет страницу товара на
+ вашем сайте. В этом режиме Telecart работает как каталог.
+
+
+
+ Выберите, какие товары показывать на главной странице магазина в Telegram.
+ Это влияет на первую видимую секцию каталога для пользователя.
+
+
+
+ На главной странице будут отображаться избранные товары, если вы выберете этот вариант в
+ настройке “Товары на главной”. Если товары не выбраны, то будут показаны популярные товары.
+
+
+
+ Выберите, какие товары показывать на главной странице магазина в Telegram.
+ Это влияет на первую видимую секцию каталога для пользователя.
+
+
+
+ На главной странице будут отображаться эти категории,
+ если вы выберете этот вариант в настройке “Категории на главной”.
+
+
+
+
+ Позволяет использовать стандартные
+ купоны OpenCart
+ для предоставления скидок при оформлении заказа.
+
+
+
+
+ Позволяет использовать стандартные
+ подарочные сертификаты OpenCart при оформлении заказа.
+
+
+
+
diff --git a/frontend/admin/src/views/TelegramView.vue b/frontend/admin/src/views/TelegramView.vue
new file mode 100644
index 0000000..a35e8bd
--- /dev/null
+++ b/frontend/admin/src/views/TelegramView.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+ Введите шаблон сообщения для Telegram-уведомлений о новом заказе владельцу магазина.
+
+
+ Введите шаблон сообщения для Telegram-уведомлений о новом заказе покупателю.
+
+
+
+
diff --git a/frontend/admin/src/views/TextsView.vue b/frontend/admin/src/views/TextsView.vue
new file mode 100644
index 0000000..0150260
--- /dev/null
+++ b/frontend/admin/src/views/TextsView.vue
@@ -0,0 +1,21 @@
+
+
+ Текст, отображаемый в конце списка, когда больше нет доступных товаров.
+ Покупатель дошел до конца списка.
+
+
+
+ Текст, отображаемый на странице просмотра корзины, если в ней нет товаров.
+
+
+
+ Текст, отображаемый при успешном создании заказа.
+
+
+
+
diff --git a/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php b/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php
index 86f1fe3..a57f9fd 100755
--- a/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php
+++ b/module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php
@@ -1,8 +1,12 @@
config->get('module_tgshop_app_name') !== null;
-
- if ($hasConfig) {
- $this->updateConfigFromDefaults();
- $this->cleanUpOldAssets();
- $this->injectVueJs();
- $this->config();
- } else {
- $this->init();
- }
+ $this->cleanUpOldAssets();
+ $this->migrateFromOldSettings();
+ $this->removeLegacyFiles();
+ $this->injectVueJs();
+ $this->config();
}
private function config(): void
@@ -107,142 +106,78 @@ class ControllerExtensionModuleTgshop extends Controller
$data = [];
$this->document->setTitle($this->language->get('heading_title'));
- if (($this->request->server['REQUEST_METHOD'] === 'POST') && $this->validate()) {
- $postData = $this->request->post;
- $postData['module_tgshop_mainpage_slider'] = [];
- if (! empty($_POST['module_tgshop_mainpage_slider'])) {
- $postData['module_tgshop_mainpage_slider'] = $_POST['module_tgshop_mainpage_slider'];
- }
- $this->model_setting_setting->editSetting('module_tgshop', $postData);
-
- $this->session->data['success'] = $this->language->get('text_success');
-
- $this->response->redirect(
- $this->url->link(
- 'extension/module/tgshop',
- 'user_token=' . $this->session->data['user_token'] . '&type=module',
- true
- )
- );
- }
-
$this->baseData($data);
+ $data['order_statuses'] = $this->getOrderStatuses();
+ $data['customer_groups'] = $this->getCustomerGroups();
+ $data['themes'] = self::$themes;
+
$data['action'] = $this->url->link(
'extension/module/tgshop',
'user_token=' . $this->session->data['user_token'],
true
);
- $data['settings'] = $this->getSettingsConfig();
-
- $data['mainpage_slider'] = [];
- $banners = $this->config->get('module_tgshop_mainpage_slider');
- if ($banners) {
- $banners = html_entity_decode($banners);
- $data['mainpage_slider'] = $banners;
- }
-
- foreach ($data['settings'] as $configs) {
- foreach ($configs as $key => $config) {
- if ($config['type'] === 'image') {
- if (isset($this->request->post[$key]) && is_file(DIR_IMAGE . $this->request->post[$key])) {
- $data[$key] = $this->model_tool_image->resize($this->request->post[$key], 100, 100);
- } elseif ($this->config->get($key) && is_file(DIR_IMAGE . $this->config->get($key))) {
- $data[$key] = $this->model_tool_image->resize($this->config->get($key), 100, 100);
- } else {
- $data[$key] = $this->model_tool_image->resize('no_image.png', 100, 100);
- }
- } elseif ($config['type'] === 'products') {
- $products = $this->request->post[$key] ?? $this->config->get($key) ?? [];
-
- $data[$key] = [];
- foreach ($products as $productId) {
- $productItem = $this->model_catalog_product->getProduct($productId);
- $data[$key][] = [
- 'product_id' => $productId,
- 'name' => $productItem['name'],
- ];
- }
- } elseif ($config['type'] === 'categories') {
- $categories = $this->request->post[$key] ?? $this->config->get($key) ?? [];
-
- $data[$key] = [];
- foreach ($categories as $categoryId) {
- $categoryItem = $this->model_catalog_category->getCategory($categoryId);
- $data[$key][] = [
- 'category_id' => $categoryId,
- 'name' => $categoryItem['name'],
- ];
- }
- } elseif (isset($this->request->post[$key])) {
- $data[$key] = $this->request->post[$key];
- } else {
- $data[$key] = $this->config->get($key);
- }
- }
- }
-
$this->response->setOutput($this->load->view('extension/module/tgshop', $data));
}
- public function init(): void
- {
- $data = [];
- $this->baseData($data);
-
- $data['action'] = $this->url->link(
- 'extension/module/tgshop/init',
- 'user_token=' . $this->session->data['user_token'],
- true
- );
-
- if ($this->request->server['REQUEST_METHOD'] === 'POST') {
- $defaults = $this->getDefaultConfig();
- $this->model_setting_setting->editSetting('module_tgshop', $defaults);
- $this->session->data['success'] = 'Инициализация модуля выполнена успешно.';
- $this->response->redirect(
- $this->url->link(
- 'extension/module/tgshop',
- 'user_token=' . $this->session->data['user_token'],
- true
- )
- );
- }
-
- $this->response->setOutput($this->load->view('extension/module/tgshop_init', $data));
- }
-
public function handle(): void
{
- $app = ApplicationFactory::create([
- 'base_url' => HTTPS_SERVER,
- 'public_url' => HTTPS_CATALOG,
- 'telegram' => [
- 'bot_token' => $this->config->get('module_tgshop_bot_token'),
- 'chat_id' => $this->config->get('module_tgshop_chat_id'),
- 'owner_notification_template' => $this->config->get('module_tgshop_owner_notification_template'),
- 'customer_notification_template' => $this->config->get('module_tgshop_customer_notification_template'),
- ],
- 'db' => [
- 'host' => DB_HOSTNAME,
- 'database' => DB_DATABASE,
- 'username' => DB_USERNAME,
- 'password' => DB_PASSWORD,
- 'prefix' => DB_PREFIX,
- 'port' => DB_PORT,
- ],
- 'logs' => [
- 'path' => DIR_LOGS,
- ],
- ]);
+ try {
+ $json = $this->model_setting_setting->getSetting('module_telecart');
+ if (! isset($json['module_telecart_settings'])) {
+ $json['module_telecart_settings'] = [];
+ }
+ $items = Arr::mergeArraysRecursively($json['module_telecart_settings'], [
+ 'app' => [
+ 'shop_base_url' => HTTPS_CATALOG, // for catalog: HTTPS_SERVER, for admin: HTTPS_CATALOG
+ 'language_id' => (int) $this->config->get('config_language_id'),
+ ],
+ 'logs' => [
+ 'path' => DIR_LOGS,
+ ],
+ 'database' => [
+ 'host' => DB_HOSTNAME,
+ 'database' => DB_DATABASE,
+ 'username' => DB_USERNAME,
+ 'password' => DB_PASSWORD,
+ 'prefix' => DB_PREFIX,
+ 'port' => (int) DB_PORT,
+ ],
+ 'store' => [
+ 'oc_store_id' => 0,
+ 'oc_default_currency' => $this->config->get('config_currency'),
+ 'oc_config_tax' => filter_var($this->config->get('config_tax'), FILTER_VALIDATE_BOOLEAN),
+ ],
+ 'orders' => [
+ 'oc_customer_group_id' => (int) $this->config->get('config_customer_group_id'),
+ ],
+ 'telegram' => [
+ 'mini_app_url' => rtrim(HTTPS_CATALOG, '/') . '/image/catalog/tgshopspa/#/',
+ ],
+ ]);
- $app->bind(OcRegistryDecorator::class, fn() => new OcRegistryDecorator($this->registry));
+ $app = ApplicationFactory::create($items);
+ $app->bind(OcRegistryDecorator::class, fn() => new OcRegistryDecorator($this->registry));
- $app
- ->withLogger(fn() => new OpenCartLogAdapter($this->log, 'TeleCartAdmin'))
- ->bootAndHandleRequest();
+ $app
+ ->withLogger(fn() => new OpenCartLogAdapter(
+ $this->log,
+ 'TeleCartAdmin',
+ $app->getConfigValue('app.app_debug')
+ ? LoggerInterface::LEVEL_DEBUG
+ : LoggerInterface::LEVEL_WARNING,
+ ))
+ ->bootAndHandleRequest();
+ } catch (Exception $e) {
+ $this->log->write('[TELECART] Error: ' . $e->getMessage());
+ http_response_code(HttpResponse::HTTP_INTERNAL_SERVER_ERROR);
+ header('Content-Type: application/json');
+ echo json_encode([
+ 'error' => 'Ошибка сервера. Приносим свои извинения за неудобства.',
+ ], JSON_THROW_ON_ERROR);
+ }
}
protected function validate(): bool
@@ -251,16 +186,6 @@ class ControllerExtensionModuleTgshop extends Controller
$this->error['telecart_error_warning'] = $this->language->get('error_permission');
}
- foreach ($this->getSettingsConfig() as $configs) {
- foreach ($configs as $key => $config) {
- if (($config['required'] ?? false) === true && ! $this->request->post[$key]) {
- $this->error["error_$key"] = 'Поле "' . $this->language->get(
- "lbl_$key"
- ) . '" обязательно для заполнения.';
- }
- }
- }
-
return ! $this->error;
}
@@ -316,286 +241,6 @@ class ControllerExtensionModuleTgshop extends Controller
$data['user_token'] = $this->session->data['user_token'];
}
- private function getDefaultConfig(): array
- {
- return [
- 'module_tgshop_status' => 1,
- 'module_tgshop_debug' => 0,
- 'module_tgshop_app_name' => $this->config->get('config_meta_title'),
- 'module_tgshop_app_icon' => $this->config->get('config_image') ?: $this->model_tool_image->resize(
- 'no_image.png',
- 100,
- 100
- ),
- 'module_tgshop_owner_notification_template' => << << 'light',
- 'module_tgshop_theme_dark' => 'dark',
- 'module_tgshop_mainpage_products' => 'most_viewed',
- 'module_tgshop_order_customer_group_id' => 1,
- 'module_tgshop_order_default_status_id' => 1,
- 'module_tgshop_mini_app_url' => rtrim(HTTPS_CATALOG, '/') . '/image/catalog/tgshopspa/#/',
- 'module_tgshop_mainpage_categories' => 'latest10',
- 'module_tgshop_enable_store' => 1,
- 'module_tgshop_feature_coupons' => 0,
- 'module_tgshop_feature_vouchers' => 0,
- 'module_tgshop_text_no_more_products' => 'Это всё по текущему запросу. Попробуйте уточнить фильтры или поиск.',
- 'module_tgshop_text_empty_cart' => 'Ваша корзина пуста',
- 'module_tgshop_text_order_created_success' => 'Ваш заказ успешно оформлен и будет обработан в ближайшее время.',
- 'module_tgshop_mainpage_slider' => json_encode([
- 'is_enabled' => false,
- 'effect' => 'slide',
- 'pagination' => true,
- 'scrollbar' => false,
- 'free_mode' => false,
- 'space_between' => 30,
- 'autoplay' => false,
- 'loop' => false,
- 'slides' => [],
- ], JSON_THROW_ON_ERROR),
- 'module_tgshop_yandex_metrika' => '',
- 'module_tgshop_chat_id' => '',
- 'module_tgshop_bot_token' => '',
- ];
- }
-
- private function getSettingsConfig(): array
- {
- $ocCouponsLink = $this->url->link(
- 'marketing/coupon',
- 'user_token=' . $this->session->data['user_token'],
- true
- );
- $ocVouchersLink = $this->url->link(
- 'sale/voucher',
- 'user_token=' . $this->session->data['user_token'],
- true
- );
-
- return [
- 'general' => [
- 'module_tgshop_status' => [
- 'type' => 'select',
- 'options' => [
- 0 => 'Выключено',
- 1 => 'Включено',
- ],
- 'help' => 'Если выключено, покупатели в Telegram увидят сообщение, что магазин временно закрыт. Заказы и просмотр товаров будут недоступны.',
- ],
-
- 'module_tgshop_app_name' => [
- 'type' => 'text',
- 'placeholder' => 'Без названия',
- 'help' => << [
- 'type' => 'image',
- 'help' => << [
- 'type' => 'select',
- 'options' => static::$themes,
- 'help' => 'Выберите стиль, который будет использоваться при отображении вашего магазина в Telegram для дневного режима. Посмотреть как выглядят темы ',
- ],
-
- 'module_tgshop_theme_dark' => [
- 'type' => 'select',
- 'options' => static::$themes,
- 'help' => 'Выберите стиль, который будет использоваться при отображении вашего магазина в Telegram для ночного режима. Посмотреть как выглядят темы ',
- ],
-
- 'module_tgshop_debug' => [
- 'type' => 'select',
- 'options' => [
- 0 => 'Выключено',
- 1 => 'Включено',
- ],
- 'help' => 'Режим разработчика. Рекомендуется включать только по необходимости. В остальных случаях, для нормальной работы магазина, должен быть выключен.',
- ],
- ],
- 'telegram' => [
- 'module_tgshop_mini_app_url' => [
- 'type' => 'text_readonly',
- 'help' => <<⚠️ Важно: ссылка обязательно должна заканчиваться на /#/ — иначе приложение не загрузится.
-HTML,
- ],
-
- 'module_tgshop_bot_token' => [
- 'type' => 'bot_token',
- 'placeholder' => 'Введите токен от телеграм бота',
- 'help' => << [
- 'type' => 'chatid',
- 'placeholder' => 'Введите Chat ID',
- 'help' => << [
- 'type' => 'tg_message_template',
- 'placeholder' => 'Введите текст уведомления',
- 'rows' => 15,
- 'help' => 'Введите шаблон сообщения для Telegram-уведомлений о новом заказе владельцу магазина.',
- ],
- 'module_tgshop_customer_notification_template' => [
- 'type' => 'tg_message_template',
- 'placeholder' => 'Введите текст уведомления',
- 'rows' => 15,
- 'help' => 'Введите шаблон сообщения для Telegram-уведомлений о новом заказе покупателю.',
- ],
- ],
- 'statistics' => [
- 'module_tgshop_yandex_metrika' => [
- 'type' => 'textarea',
- 'placeholder' => 'Вставьте код счётчика Яндекс Метрики.',
- 'rows' => 15,
- 'help' => 'Для проверки интеграции через кнопку "Проверить" в интерфейсе Яндекс Метрики, необходимо сначала включить "Режим разработчика" на вкладке "Общие".'
- ],
- ],
- 'shop' => [
- 'module_tgshop_enable_store' => [
- 'type' => 'select',
- 'options' => [
- 0 => 'Выключено',
- 1 => 'Включено',
- ],
- 'help' => <<включена — пользователи смогут оформлять заказы прямо в Telegram-магазине.
-Если выключена — оформление заказов будет недоступно. Вместо кнопки «Добавить в корзину» пользователи увидят кнопку «Перейти к товару», которая откроет страницу товара на вашем сайте. В этом режиме Telecart работает как каталог.
-HTML,
- ],
- 'module_tgshop_mainpage_products' => [
- 'type' => 'select',
- 'options' => [
- 'most_viewed' => 'Популярные товары',
- 'latest' => 'Последние сверху',
- 'featured' => 'Избранные товары (задать в поле ниже)',
- ],
- 'help' => 'Выберите, какие товары показывать на главной странице магазина в Telegram. Это влияет на первую видимую секцию каталога для пользователя.',
- ],
-
- 'module_tgshop_featured_products' => [
- 'type' => 'products',
- 'help' => 'На главной странице будут отображаться избранные товары, если вы выберете этот вариант в настройке “Товары на главной”. Если товары не выбраны, то будут показаны популярные товары.',
- ],
-
- 'module_tgshop_mainpage_categories' => [
- 'type' => 'select',
- 'options' => [
- 'no_categories' => 'Отображать только кнопку "Каталог"',
- 'latest10' => 'Последние 10 категорий',
- 'featured' => 'Избранные категории (задать в поле ниже)',
- ],
- 'help' => 'Выберите, какие категории показывать на главной странице магазина в Telegram. Это влияет на первую видимую секцию каталога для пользователя.',
- ],
-
- 'module_tgshop_featured_categories' => [
- 'type' => 'categories',
- 'help' => 'На главной странице будут отображаться эти категории, если вы выберете этот вариант в настройке “Категории на главной”.',
- ],
-
- 'module_tgshop_feature_coupons' => [
- 'type' => 'select',
- 'options' => [
- 0 => 'Выключено',
- 1 => 'Включено',
- ],
- 'help' => <<купоны OpenCart для предоставления скидок при оформлении заказа.
-HTML,
- ],
-
- 'module_tgshop_feature_vouchers' => [
- 'type' => 'select',
- 'options' => [
- 0 => 'Выключено',
- 1 => 'Включено',
- ],
- 'help' => <<подарочные сертификаты OpenCart при оформлении заказа.
-HTML,
- ],
- ],
- 'orders' => [
- 'module_tgshop_order_default_status_id' => [
- 'type' => 'select',
- 'options' => $this->getOrderStatuses(),
- 'help' => 'Статус, с которым будут создаваться заказы через Telegram по умолчанию.',
- ],
-
- 'module_tgshop_order_customer_group_id' => [
- 'hidden' => true,
- 'type' => 'select',
- 'options' => $this->getCustomerGroups(),
- 'help' => 'Группа покупателей, которая будет назначена для заказов, оформленных через Telegram-магазин.',
- ],
- ],
-
- 'texts' => [
- 'module_tgshop_text_no_more_products' => [
- 'type' => 'text',
- 'placeholder' => 'Это всё по текущему запросу. Попробуйте уточнить фильтры или поиск.',
- 'help' => 'Текст, отображаемый в конце списка, когда больше нет доступных товаров. Покупатель дошел до конца списка.',
- ],
-
- 'module_tgshop_text_empty_cart' => [
- 'type' => 'text',
- 'placeholder' => 'Ваша корзина пуста',
- 'help' => 'Текст, отображаемый на странице просмотра корзины, если в ней нет товаров.',
- ],
-
- 'module_tgshop_text_order_created_success' => [
- 'type' => 'text',
- 'placeholder' => 'Ваш заказ успешно оформлен и будет обработан в ближайшее время.',
- 'help' => 'Текст, отображаемый при успешном создании заказа.',
- ],
- ],
- ];
- }
-
private function getCustomerGroups(): array
{
$map = [];
@@ -607,7 +252,7 @@ HTML,
return $map;
}
- private function getOrderStatuses()
+ private function getOrderStatuses(): array
{
$statuses = $this->model_localisation_order_status->getOrderStatuses();
$map = [];
@@ -619,45 +264,6 @@ HTML,
return $map;
}
- private function updateConfigFromDefaults(): void
- {
- $defaults = $this->getDefaultConfig();
- $settings = $this->model_setting_setting->getSetting('module_tgshop');
-
- $diff = [];
- foreach ($defaults as $key => $value) {
- if (! array_key_exists($key, $settings)) {
- $diff[$key] = $defaults[$key];
- }
- }
-
- if ($diff) {
- $settings = array_merge($settings, $diff);
- $this->model_setting_setting->editSetting('module_tgshop', $settings);
- $this->log->write('[TELECART] Выполнено обновление настроек по умолчанию для модуля.');
- $this->session->data['success'] = 'Выполнено обновление настроек по умолчанию для модуля.';
-
- foreach ($diff as $key => $value) {
- $this->config->set($key, $value);
- }
- }
-
- $diffToDelete = [];
- foreach ($settings as $key => $value) {
- if (! array_key_exists($key, $defaults)) {
- $diffToDelete[] = $key;
- }
- }
-
- if ($diffToDelete) {
- $keys = implode(', ', array_map(function ($key) {
- return "'{$key}'";
- }, $diffToDelete));
- $this->db->query("DELETE FROM " . DB_PREFIX . "setting WHERE `key` IN ($keys)");
- $this->log->write('[TELECART] Удалены старые конфиги: ' . $keys);
- }
- }
-
private function cleanUpOldAssets(): void
{
$spaPath = rtrim(DIR_IMAGE, '/') . '/catalog/tgshopspa';
@@ -733,4 +339,95 @@ HTML,
throw new RuntimeException('Unable to load Vuejs frontend.');
}
}
+
+ private function migrateFromOldSettings(): void
+ {
+ $legacySettings = $this->model_setting_setting->getSetting('module_tgshop');
+ if (! $legacySettings) {
+ return;
+ }
+
+ $newSettings = $this->model_setting_setting->getSetting('module_telecart');
+
+ static $mapLegacyToNewSettings = [
+ 'module_tgshop_app_icon' => 'app.app_icon',
+ 'module_tgshop_theme_light' => 'app.theme_light',
+ 'module_tgshop_bot_token' => 'telegram.bot_token',
+ 'module_tgshop_status' => 'app.app_enabled',
+ 'module_tgshop_app_name' => 'app.app_name',
+ 'module_tgshop_theme_dark' => 'app.theme_dark',
+ 'module_tgshop_debug' => 'app.app_debug',
+ 'module_tgshop_chat_id' => 'telegram.chat_id',
+ 'module_tgshop_owner_notification_template' => 'telegram.owner_notification_template',
+ 'module_tgshop_text_order_created_success' => 'texts.text_order_created_success',
+ 'module_tgshop_enable_store' => 'store.enable_store',
+ 'module_tgshop_mainpage_products' => 'store.mainpage_products',
+ 'module_tgshop_yandex_metrika' => 'metrics.yandex_metrika_counter',
+ 'module_tgshop_customer_notification_template' => 'telegram.customer_notification_template',
+ 'module_tgshop_feature_vouchers' => 'store.feature_vouchers',
+ 'module_tgshop_order_default_status_id' => 'orders.order_default_status_id',
+ 'module_tgshop_feature_coupons' => 'store.feature_coupons',
+ 'module_tgshop_mainpage_categories' => 'store.mainpage_categories',
+ 'module_tgshop_text_no_more_products' => 'texts.text_no_more_products',
+ 'module_tgshop_text_empty_cart' => 'texts.text_empty_cart',
+ ];
+
+ if (! $newSettings) {
+ $data = [];
+ Arr::set($data, 'app.app_icon', $legacySettings['module_tgshop_app_icon']);
+
+ foreach ($mapLegacyToNewSettings as $key => $value) {
+ if (array_key_exists($key, $legacySettings)) {
+ if ($key === 'module_tgshop_status') {
+ $newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
+ } elseif ($key === 'module_tgshop_debug') {
+ $newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
+ } elseif ($key === 'module_tgshop_chat_id') {
+ $newValue = (int) $legacySettings[$key];
+ } elseif ($key === 'module_tgshop_enable_store') {
+ $newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
+ } elseif ($key === 'module_tgshop_order_default_status_id') {
+ $newValue = (int) $legacySettings[$key];
+ } elseif ($key === 'module_tgshop_feature_vouchers') {
+ $newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
+ } elseif ($key === 'module_tgshop_feature_coupons') {
+ $newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
+ } else {
+ $newValue = $legacySettings[$key];
+ }
+
+ Arr::set($data, $value, $newValue);
+ }
+ }
+
+ Arr::set(
+ $data,
+ 'metrics.yandex_metrika_enabled',
+ ! empty(trim($legacySettings['module_tgshop_yandex_metrika']))
+ );
+
+ $this->model_setting_setting->editSetting('module_telecart', [
+ 'module_telecart_settings' => $data,
+ ]);
+
+ $this->log->write('[TELECART] Выполнено обновление настроек с 1й версии модуля.');
+ $this->session->data['success'] = 'Выполнено обновление настроек с прошлой версии модуля.';
+ }
+
+ $this->model_setting_setting->deleteSetting('module_tgshop');
+ }
+
+ private function removeLegacyFiles(): void
+ {
+ $legacyFilesToRemove = [
+ DIR_TEMPLATE . '/extension/module/tgshop_init.twig',
+ ];
+
+ foreach ($legacyFilesToRemove as $file) {
+ if (file_exists($file)) {
+ unlink($file);
+ $this->log->write('[TELECART] Удалён старый файл: ' . $file);
+ }
+ }
+ }
}
diff --git a/module/oc_telegram_shop/upload/admin/view/template/extension/module/tgshop.twig b/module/oc_telegram_shop/upload/admin/view/template/extension/module/tgshop.twig
index d45027b..e264e63 100755
--- a/module/oc_telegram_shop/upload/admin/view/template/extension/module/tgshop.twig
+++ b/module/oc_telegram_shop/upload/admin/view/template/extension/module/tgshop.twig
@@ -2,11 +2,6 @@
@@ -467,7 +42,7 @@