Создан поиск, уведомления, настройки, мониторгин, админ-панель

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2021-02-24 08:24:57 +10:00
parent 0f8e6a7a6a
commit 8a3897ad8f
169 changed files with 235856 additions and 930 deletions

251
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "7219e565f7400643e10772d84e87d8a5", "content-hash": "f259164a4251a4ef37262d42fbeb40d6",
"packages": [ "packages": [
{ {
"name": "bower-asset/bootstrap", "name": "bower-asset/bootstrap",
@ -32,7 +32,7 @@
"version": "3.3.11", "version": "3.3.11",
"source": { "source": {
"type": "git", "type": "git",
"url": "git@github.com:RobinHerbots/Inputmask.git", "url": "https://github.com/RobinHerbots/Inputmask.git",
"reference": "5e670ad62f50c738388d4dcec78d2888505ad77b" "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b"
}, },
"dist": { "dist": {
@ -53,7 +53,7 @@
"version": "3.5.1", "version": "3.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "git@github.com:jquery/jquery-dist.git", "url": "https://github.com/jquery/jquery-dist.git",
"reference": "4c0e4becb8263bb5b3e6dadc448d8e7305ef8215" "reference": "4c0e4becb8263bb5b3e6dadc448d8e7305ef8215"
}, },
"dist": { "dist": {
@ -643,20 +643,20 @@
}, },
{ {
"name": "markbaker/matrix", "name": "markbaker/matrix",
"version": "2.0.0", "version": "2.1.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git", "url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "9567d9c4c519fbe40de01dbd1e4469dbbb66f46a" "reference": "361c0f545c3172ee26c3d596a0aa03f0cef65e6a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/9567d9c4c519fbe40de01dbd1e4469dbbb66f46a", "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/361c0f545c3172ee26c3d596a0aa03f0cef65e6a",
"reference": "9567d9c4c519fbe40de01dbd1e4469dbbb66f46a", "reference": "361c0f545c3172ee26c3d596a0aa03f0cef65e6a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.2 || ^8.0" "php": "^7.1 || ^8.0"
}, },
"require-dev": { "require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
@ -674,22 +674,22 @@
"Matrix\\": "classes/src/" "Matrix\\": "classes/src/"
}, },
"files": [ "files": [
"classes/src/functions/adjoint.php", "classes/src/Functions/adjoint.php",
"classes/src/functions/antidiagonal.php", "classes/src/Functions/antidiagonal.php",
"classes/src/functions/cofactors.php", "classes/src/Functions/cofactors.php",
"classes/src/functions/determinant.php", "classes/src/Functions/determinant.php",
"classes/src/functions/diagonal.php", "classes/src/Functions/diagonal.php",
"classes/src/functions/identity.php", "classes/src/Functions/identity.php",
"classes/src/functions/inverse.php", "classes/src/Functions/inverse.php",
"classes/src/functions/minors.php", "classes/src/Functions/minors.php",
"classes/src/functions/trace.php", "classes/src/Functions/trace.php",
"classes/src/functions/transpose.php", "classes/src/Functions/transpose.php",
"classes/src/operations/add.php", "classes/src/Operations/add.php",
"classes/src/operations/directsum.php", "classes/src/Operations/directsum.php",
"classes/src/operations/subtract.php", "classes/src/Operations/subtract.php",
"classes/src/operations/multiply.php", "classes/src/Operations/multiply.php",
"classes/src/operations/divideby.php", "classes/src/Operations/divideby.php",
"classes/src/operations/divideinto.php" "classes/src/Operations/divideinto.php"
] ]
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@ -711,17 +711,17 @@
], ],
"support": { "support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues", "issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/PHP8" "source": "https://github.com/MarkBaker/PHPMatrix/tree/2.1.2"
}, },
"time": "2020-08-28T17:11:00+00:00" "time": "2021-01-23T16:37:31+00:00"
}, },
{ {
"name": "mirzaev/yii2-arangodb", "name": "mirzaev/yii2-arangodb",
"version": "2.1.0.x-dev", "version": "2.1.x-dev",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://git.hood.su/mirzaev/yii2/arangodb", "url": "https://git.hood.su/mirzaev/yii2/arangodb",
"reference": "3355611e2008933d647e96316b2a488a7be705c5" "reference": "bf4483291486eb0b991907c3e55e5392f84f2ed9"
}, },
"require": { "require": {
"php": "^8.0.0", "php": "^8.0.0",
@ -763,7 +763,7 @@
"ArangoDb", "ArangoDb",
"yii2" "yii2"
], ],
"time": "2021-01-20T03:59:35+00:00" "time": "2021-01-31T01:36:27+00:00"
}, },
{ {
"name": "moonlandsoft/yii2-phpexcel", "name": "moonlandsoft/yii2-phpexcel",
@ -2130,25 +2130,26 @@
"packages-dev": [ "packages-dev": [
{ {
"name": "behat/gherkin", "name": "behat/gherkin",
"version": "v4.6.2", "version": "v4.8.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Behat/Gherkin.git", "url": "https://github.com/Behat/Gherkin.git",
"reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31" "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Behat/Gherkin/zipball/51ac4500c4dc30cbaaabcd2f25694299df666a31", "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2391482cd003dfdc36b679b27e9f5326bd656acd",
"reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31", "reference": "2391482cd003dfdc36b679b27e9f5326bd656acd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.1" "php": "~7.2|~8.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "~4.5|~5", "cucumber/cucumber": "dev-gherkin-16.0.0",
"symfony/phpunit-bridge": "~2.7|~3|~4", "phpunit/phpunit": "~8|~9",
"symfony/yaml": "~2.3|~3|~4" "symfony/phpunit-bridge": "~3|~4|~5",
"symfony/yaml": "~3|~4|~5"
}, },
"suggest": { "suggest": {
"symfony/yaml": "If you want to parse features, represented in YAML files" "symfony/yaml": "If you want to parse features, represented in YAML files"
@ -2175,7 +2176,7 @@
"homepage": "http://everzet.com" "homepage": "http://everzet.com"
} }
], ],
"description": "Gherkin DSL parser for PHP 5.3", "description": "Gherkin DSL parser for PHP",
"homepage": "http://behat.org/", "homepage": "http://behat.org/",
"keywords": [ "keywords": [
"BDD", "BDD",
@ -2187,22 +2188,22 @@
], ],
"support": { "support": {
"issues": "https://github.com/Behat/Gherkin/issues", "issues": "https://github.com/Behat/Gherkin/issues",
"source": "https://github.com/Behat/Gherkin/tree/master" "source": "https://github.com/Behat/Gherkin/tree/v4.8.0"
}, },
"time": "2020-03-17T14:03:26+00:00" "time": "2021-02-04T12:44:21+00:00"
}, },
{ {
"name": "codeception/codeception", "name": "codeception/codeception",
"version": "4.1.15", "version": "4.1.17",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Codeception/Codeception.git", "url": "https://github.com/Codeception/Codeception.git",
"reference": "9b174d18ba58bb2e8cc4cecce619d6124df1d83a" "reference": "c153b1ab289b3e3109e685379aa8847c54ac2b68"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/9b174d18ba58bb2e8cc4cecce619d6124df1d83a", "url": "https://api.github.com/repos/Codeception/Codeception/zipball/c153b1ab289b3e3109e685379aa8847c54ac2b68",
"reference": "9b174d18ba58bb2e8cc4cecce619d6124df1d83a", "reference": "c153b1ab289b3e3109e685379aa8847c54ac2b68",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2276,7 +2277,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/Codeception/Codeception/issues", "issues": "https://github.com/Codeception/Codeception/issues",
"source": "https://github.com/Codeception/Codeception/tree/4.1.15" "source": "https://github.com/Codeception/Codeception/tree/4.1.17"
}, },
"funding": [ "funding": [
{ {
@ -2284,7 +2285,7 @@
"type": "open_collective" "type": "open_collective"
} }
], ],
"time": "2021-01-17T19:19:40+00:00" "time": "2021-02-01T07:30:47+00:00"
}, },
{ {
"name": "codeception/lib-asserts", "name": "codeception/lib-asserts",
@ -2342,16 +2343,16 @@
}, },
{ {
"name": "codeception/lib-innerbrowser", "name": "codeception/lib-innerbrowser",
"version": "1.3.6", "version": "1.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Codeception/lib-innerbrowser.git", "url": "https://github.com/Codeception/lib-innerbrowser.git",
"reference": "41b79ba6761001bdb1f373a347400180693ad4e7" "reference": "b7406c710684c255d9b067d7795269a5585a0406"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Codeception/lib-innerbrowser/zipball/41b79ba6761001bdb1f373a347400180693ad4e7", "url": "https://api.github.com/repos/Codeception/lib-innerbrowser/zipball/b7406c710684c255d9b067d7795269a5585a0406",
"reference": "41b79ba6761001bdb1f373a347400180693ad4e7", "reference": "b7406c710684c255d9b067d7795269a5585a0406",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2396,9 +2397,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/Codeception/lib-innerbrowser/issues", "issues": "https://github.com/Codeception/lib-innerbrowser/issues",
"source": "https://github.com/Codeception/lib-innerbrowser/tree/1.3.6" "source": "https://github.com/Codeception/lib-innerbrowser/tree/1.4.0"
}, },
"time": "2021-01-17T11:21:09+00:00" "time": "2021-01-29T18:17:25+00:00"
}, },
{ {
"name": "codeception/module-asserts", "name": "codeception/module-asserts",
@ -3938,16 +3939,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "9.5.1", "version": "9.5.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360" "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7bdf4085de85a825f4424eae52c99a1cec2f360", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f661659747f2f87f9e72095bb207bceb0f151cb4",
"reference": "e7bdf4085de85a825f4424eae52c99a1cec2f360", "reference": "f661659747f2f87f9e72095bb207bceb0f151cb4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4025,7 +4026,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.1" "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.2"
}, },
"funding": [ "funding": [
{ {
@ -4037,7 +4038,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2021-01-17T07:42:25+00:00" "time": "2021-02-02T14:45:58+00:00"
}, },
{ {
"name": "psr/container", "name": "psr/container",
@ -5152,16 +5153,16 @@
}, },
{ {
"name": "symfony/browser-kit", "name": "symfony/browser-kit",
"version": "v5.2.1", "version": "v5.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/browser-kit.git", "url": "https://github.com/symfony/browser-kit.git",
"reference": "87d6f0a7436b03a57d4cf9a6a9cd0c83a355c49a" "reference": "b03b2057ed53ee4eab2e8f372084d7722b7b8ffd"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/87d6f0a7436b03a57d4cf9a6a9cd0c83a355c49a", "url": "https://api.github.com/repos/symfony/browser-kit/zipball/b03b2057ed53ee4eab2e8f372084d7722b7b8ffd",
"reference": "87d6f0a7436b03a57d4cf9a6a9cd0c83a355c49a", "reference": "b03b2057ed53ee4eab2e8f372084d7722b7b8ffd",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5200,10 +5201,10 @@
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony BrowserKit Component", "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/browser-kit/tree/v5.2.1" "source": "https://github.com/symfony/browser-kit/tree/v5.2.3"
}, },
"funding": [ "funding": [
{ {
@ -5219,20 +5220,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-12-18T08:03:05+00:00" "time": "2021-01-27T12:56:27+00:00"
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v5.2.1", "version": "v5.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "47c02526c532fb381374dab26df05e7313978976" "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/47c02526c532fb381374dab26df05e7313978976", "url": "https://api.github.com/repos/symfony/console/zipball/89d4b176d12a2946a1ae4e34906a025b7b6b135a",
"reference": "47c02526c532fb381374dab26df05e7313978976", "reference": "89d4b176d12a2946a1ae4e34906a025b7b6b135a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5291,7 +5292,7 @@
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony Console Component", "description": "Eases the creation of beautiful and testable command line interfaces",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"keywords": [ "keywords": [
"cli", "cli",
@ -5300,7 +5301,7 @@
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v5.2.1" "source": "https://github.com/symfony/console/tree/v5.2.3"
}, },
"funding": [ "funding": [
{ {
@ -5316,20 +5317,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-12-18T08:03:05+00:00" "time": "2021-01-28T22:06:19+00:00"
}, },
{ {
"name": "symfony/css-selector", "name": "symfony/css-selector",
"version": "v5.2.1", "version": "v5.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/css-selector.git", "url": "https://github.com/symfony/css-selector.git",
"reference": "f789e7ead4c79e04ca9a6d6162fc629c89bd8054" "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/f789e7ead4c79e04ca9a6d6162fc629c89bd8054", "url": "https://api.github.com/repos/symfony/css-selector/zipball/f65f217b3314504a1ec99c2d6ef69016bb13490f",
"reference": "f789e7ead4c79e04ca9a6d6162fc629c89bd8054", "reference": "f65f217b3314504a1ec99c2d6ef69016bb13490f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5362,10 +5363,10 @@
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony CssSelector Component", "description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/css-selector/tree/v5.2.1" "source": "https://github.com/symfony/css-selector/tree/v5.2.3"
}, },
"funding": [ "funding": [
{ {
@ -5381,7 +5382,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-12-08T17:02:38+00:00" "time": "2021-01-27T10:01:46+00:00"
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
@ -5452,16 +5453,16 @@
}, },
{ {
"name": "symfony/dom-crawler", "name": "symfony/dom-crawler",
"version": "v5.2.1", "version": "v5.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/dom-crawler.git", "url": "https://github.com/symfony/dom-crawler.git",
"reference": "ee7cf316fb0de786cfe5ae32ee79502b290c81ea" "reference": "5d89ceb53ec65e1973a555072fac8ed5ecad3384"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/ee7cf316fb0de786cfe5ae32ee79502b290c81ea", "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/5d89ceb53ec65e1973a555072fac8ed5ecad3384",
"reference": "ee7cf316fb0de786cfe5ae32ee79502b290c81ea", "reference": "5d89ceb53ec65e1973a555072fac8ed5ecad3384",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5503,10 +5504,10 @@
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony DomCrawler Component", "description": "Eases DOM navigation for HTML and XML documents",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/dom-crawler/tree/v5.2.1" "source": "https://github.com/symfony/dom-crawler/tree/v5.2.3"
}, },
"funding": [ "funding": [
{ {
@ -5522,20 +5523,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-12-18T08:02:46+00:00" "time": "2021-01-27T10:01:46+00:00"
}, },
{ {
"name": "symfony/event-dispatcher", "name": "symfony/event-dispatcher",
"version": "v5.2.1", "version": "v5.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher.git", "url": "https://github.com/symfony/event-dispatcher.git",
"reference": "1c93f7a1dff592c252574c79a8635a8a80856042" "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1c93f7a1dff592c252574c79a8635a8a80856042", "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4f9760f8074978ad82e2ce854dff79a71fe45367",
"reference": "1c93f7a1dff592c252574c79a8635a8a80856042", "reference": "4f9760f8074978ad82e2ce854dff79a71fe45367",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5588,10 +5589,10 @@
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony EventDispatcher Component", "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v5.2.1" "source": "https://github.com/symfony/event-dispatcher/tree/v5.2.3"
}, },
"funding": [ "funding": [
{ {
@ -5607,7 +5608,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-12-18T08:03:05+00:00" "time": "2021-01-27T10:36:42+00:00"
}, },
{ {
"name": "symfony/event-dispatcher-contracts", "name": "symfony/event-dispatcher-contracts",
@ -5690,16 +5691,16 @@
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v5.2.1", "version": "v5.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "0b9231a5922fd7287ba5b411893c0ecd2733e5ba" "reference": "4adc8d172d602008c204c2e16956f99257248e03"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/0b9231a5922fd7287ba5b411893c0ecd2733e5ba", "url": "https://api.github.com/repos/symfony/finder/zipball/4adc8d172d602008c204c2e16956f99257248e03",
"reference": "0b9231a5922fd7287ba5b411893c0ecd2733e5ba", "reference": "4adc8d172d602008c204c2e16956f99257248e03",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5728,10 +5729,10 @@
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony Finder Component", "description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/finder/tree/v5.2.1" "source": "https://github.com/symfony/finder/tree/v5.2.3"
}, },
"funding": [ "funding": [
{ {
@ -5747,7 +5748,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-12-08T17:02:38+00:00" "time": "2021-01-28T22:06:19+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
@ -6073,16 +6074,16 @@
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v5.2.1", "version": "v5.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "bd8815b8b6705298beaa384f04fabd459c10bedd" "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/bd8815b8b6705298beaa384f04fabd459c10bedd", "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f",
"reference": "bd8815b8b6705298beaa384f04fabd459c10bedd", "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -6112,10 +6113,10 @@
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony Process Component", "description": "Executes commands in sub-processes",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/process/tree/v5.2.1" "source": "https://github.com/symfony/process/tree/v5.2.3"
}, },
"funding": [ "funding": [
{ {
@ -6131,7 +6132,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-12-08T17:03:37+00:00" "time": "2021-01-27T10:15:41+00:00"
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
@ -6214,16 +6215,16 @@
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v5.2.1", "version": "v5.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed" "reference": "c95468897f408dd0aca2ff582074423dd0455122"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", "url": "https://api.github.com/repos/symfony/string/zipball/c95468897f408dd0aca2ff582074423dd0455122",
"reference": "5bd67751d2e3f7d6f770c9154b8fbcb2aa05f7ed", "reference": "c95468897f408dd0aca2ff582074423dd0455122",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -6266,7 +6267,7 @@
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony String component", "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"keywords": [ "keywords": [
"grapheme", "grapheme",
@ -6277,7 +6278,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v5.2.1" "source": "https://github.com/symfony/string/tree/v5.2.3"
}, },
"funding": [ "funding": [
{ {
@ -6293,20 +6294,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-12-05T07:33:16+00:00" "time": "2021-01-25T15:14:59+00:00"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v5.2.1", "version": "v5.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "290ea5e03b8cf9b42c783163123f54441fb06939" "reference": "338cddc6d74929f6adf19ca5682ac4b8e109cdb0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/290ea5e03b8cf9b42c783163123f54441fb06939", "url": "https://api.github.com/repos/symfony/yaml/zipball/338cddc6d74929f6adf19ca5682ac4b8e109cdb0",
"reference": "290ea5e03b8cf9b42c783163123f54441fb06939", "reference": "338cddc6d74929f6adf19ca5682ac4b8e109cdb0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -6349,10 +6350,10 @@
"homepage": "https://symfony.com/contributors" "homepage": "https://symfony.com/contributors"
} }
], ],
"description": "Symfony Yaml Component", "description": "Loads and dumps YAML files",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/yaml/tree/v5.2.1" "source": "https://github.com/symfony/yaml/tree/v5.2.3"
}, },
"funding": [ "funding": [
{ {
@ -6368,7 +6369,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-12-08T17:02:38+00:00" "time": "2021-02-03T04:42:09+00:00"
}, },
{ {
"name": "theseer/tokenizer", "name": "theseer/tokenizer",
@ -6425,12 +6426,12 @@
"version": "1.9.1", "version": "1.9.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/webmozart/assert.git", "url": "https://github.com/webmozarts/assert.git",
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
"shasum": "" "shasum": ""
}, },
@ -6468,8 +6469,8 @@
"validate" "validate"
], ],
"support": { "support": {
"issues": "https://github.com/webmozart/assert/issues", "issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozart/assert/tree/master" "source": "https://github.com/webmozarts/assert/tree/1.9.1"
}, },
"time": "2020-07-08T17:02:28+00:00" "time": "2020-07-08T17:02:28+00:00"
}, },

View File

@ -26,6 +26,7 @@ class AppAsset extends AssetBundle
'css/bootstrap/bootstrap.min.css', 'css/bootstrap/bootstrap.min.css',
'css/main.css', 'css/main.css',
'css/header.css', 'css/header.css',
'css/notification.css',
'css/info_panel.css', 'css/info_panel.css',
'css/categories_blocks_panel.css', 'css/categories_blocks_panel.css',
'css/footer.css' 'css/footer.css'
@ -39,6 +40,7 @@ class AppAsset extends AssetBundle
'js/menu.js', 'js/menu.js',
'js/account.js', 'js/account.js',
'js/search.js', 'js/search.js',
'js/notification.js',
'js/reinitialization.js' 'js/reinitialization.js'
]; ];
public $jsOptions = [ public $jsOptions = [

View File

@ -1,6 +1,8 @@
<?php <?php
$config = [ declare(strict_types=1);
return [
'id' => 'skillparts-console', 'id' => 'skillparts-console',
'basePath' => dirname(__DIR__), 'basePath' => dirname(__DIR__),
'bootstrap' => ['log'], 'bootstrap' => ['log'],
@ -9,7 +11,6 @@ $config = [
'@vendor' => dirname(__DIR__) . '/../../../vendor', '@vendor' => dirname(__DIR__) . '/../../../vendor',
'@bower' => '@vendor/bower-asset', '@bower' => '@vendor/bower-asset',
'@npm' => '@vendor/npm-asset', '@npm' => '@vendor/npm-asset',
'@explosivebit' => '@vendor/explosivebit',
'@tests' => '@app/tests', '@tests' => '@app/tests',
], ],
'components' => [ 'components' => [
@ -28,19 +29,9 @@ $config = [
], ],
'params' => require __DIR__ . '/params.php', 'params' => require __DIR__ . '/params.php',
'controllerMap' => [ 'controllerMap' => [
'arangodb-migrate' => 'explosivebit\arangodb\console\controllers\MigrateController', 'arangodb-migrate' => 'mirzaev\yii2\arangodb\console\controllers\MigrateController',
'fixture' => [ 'fixture' => [
'class' => 'yii\faker\FixtureController', 'class' => 'yii\faker\FixtureController',
], ],
] ]
]; ];
if (YII_ENV_DEV) {
// configuration adjustments for 'dev' environment
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = [
'class' => 'yii\gii\Module',
];
}
return $config;

View File

@ -49,7 +49,7 @@ class AuthenticationController extends Controller
// Запись ответа // Запись ответа
$return = [ $return = [
'menu' => (new AccountForm())->deauthenticationGenHtml(), 'menu' => $this->renderPartial('/account/deauthentication'),
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => Yii::$app->request->getCsrfToken()
]; ];
@ -93,7 +93,7 @@ class AuthenticationController extends Controller
Yii::$app->response->statusCode = 400; Yii::$app->response->statusCode = 400;
return [ return [
'main' => $this->renderPartial('/account', compact('model')), 'main' => $this->renderPartial('/account/index', compact('model')),
'redirect' => '/authentication', 'redirect' => '/authentication',
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => Yii::$app->request->getCsrfToken()
]; ];
@ -103,7 +103,7 @@ class AuthenticationController extends Controller
if (!Yii::$app->user->isGuest) { if (!Yii::$app->user->isGuest) {
Yii::$app->response->redirect('/'); Yii::$app->response->redirect('/');
} else { } else {
return $this->render('/account'); return $this->render('/account/index');
} }
} }
} }

View File

@ -2,7 +2,7 @@
namespace app\controllers; namespace app\controllers;
use Yii; use yii;
use yii\web\Controller; use yii\web\Controller;
use yii\web\Response; use yii\web\Response;
use yii\filters\AccessControl; use yii\filters\AccessControl;
@ -34,20 +34,20 @@ class DeauthenticationController extends Controller
public function actionIndex() public function actionIndex()
{ {
Yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
// Выход из аккаунта // Выход из аккаунта
Yii::$app->user->logout(); yii::$app->user->logout();
// Инициализация // Инициализация
$model = new AccountForm(Yii::$app->request->post('AccountForm') ?? Yii::$app->request->get('AccountForm') ?? null); $model = new AccountForm(yii::$app->request->post('AccountForm') ?? yii::$app->request->get('AccountForm') ?? null);
// Ответа // Ответа
return [ return [
'menu' => $model->authenticationGenHtml($this->renderPartial('/account', compact('model'))), 'menu' => $this->renderPartial('/account/authentication', compact('model')),
'main' => $this->renderPartial('/index'), 'main' => $this->renderPartial('/index'),
'redirect' => '/', 'redirect' => '/',
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
} }

View File

@ -4,8 +4,6 @@ namespace app\controllers;
use Yii; use Yii;
use yii\web\Controller; use yii\web\Controller;
use yii\web\ErrorAction;
use yii\web\Response;
class ErrorController extends Controller class ErrorController extends Controller
{ {

View File

@ -1,53 +1,56 @@
<?php <?php
declare(strict_types=1);
namespace app\controllers; namespace app\controllers;
use Yii; use yii;
use yii\web\Controller; use yii\web\Controller;
use yii\web\Response; use yii\web\Response;
use app\models\AccountForm; use app\models\AccountForm;
class IdentificationController extends Controller class IdentificationController extends Controller
{ {
public function actionIndex() public function actionIndex()
{ {
if (Yii::$app->request->isAjax) { if (yii::$app->request->isPost) {
// AJAX-POST-запрос // POST-запрос
Yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
if (Yii::$app->user->isGuest) { if (yii::$app->user->isGuest) {
// Аккаунт не аутентифицирован // Аккаунт не аутентифицирован
// Инициализация // Инициализация
$model = new AccountForm(Yii::$app->request->post('AccountForm') ?? Yii::$app->request->get('AccountForm') ?? null); $model = new AccountForm(yii::$app->request->post('AccountForm') ?? yii::$app->request->get('AccountForm') ?? null);
// Запись ответа // Запись ответа
$return = [ $return = [
'menu' => $model->authenticationGenHtml($this->renderPartial('/account', compact('model'))), 'menu' => $this->renderPartial('/account/authentication', compact('model')),
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} else { } else {
// Аккаунт аутентифицирован // Аккаунт аутентифицирован
// Инициализация // Инициализация
$model = Yii::$app->user; $model = yii::$app->user;
// Запись ответа // Запись ответа
$return = [ $return = [
'menu' => (new AccountForm())->deauthenticationGenHtml(), 'menu' => $this->renderPartial('/account/deauthentication'),
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
if (($cookies = Yii::$app->request->cookies)->has('redirect')) { if (($cookies = yii::$app->request->cookies)->has('redirect')) {
// Найдено cookie с переадресацией // Найдено cookie с переадресацией
// Запись ответа // Запись ответа
$return['redirect'] = '/' . $cookies['redirect']; $return['redirect'] = '/' . $cookies['redirect'];
// Очистка cookie // Очистка cookie
unset(Yii::$app->response->cookies['redirect']); unset(yii::$app->response->cookies['redirect']);
} }
return $return; return $return;

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace app\controllers; namespace app\controllers;
use Yii; use Yii;

View File

@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace app\controllers;
use app\models\AccountEdgeNotification;
use yii;
use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\Response;
use app\models\Notification;
class NotificationController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'only' => ['index'],
'rules' => [
[
'allow' => true,
'roles' => ['@']
]
]
]
];
}
public function actionIndex()
{
if (yii::$app->request->isPost) {
// POST-запрос
// Инициализация
$model = new Notification(yii::$app->request->post('Notification'));
yii::$app->response->format = Response::FORMAT_JSON;
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (yii::$app->request->post('last')) {
// Запрос последнего уведомлений (всплывающее окно)
$limit = 1;
} else if (yii::$app->request->post('stream')) {
// Запрос последних уведомлений (панель)
$limit = 5;
}
if (isset($limit)) {
// Обработка для всплывающего окна или панель
// Подзапрос для проверки статуса уведомления относительно пользователя
// Поиск рёбер: ПОЛЬЗОВАТЕЛЬ -> УВЕДОМЛЕНИЕ
$let = $model::find()
->for(['account', $model::collectionName() . '_edge_account'])
->traversal($model::collectionName(), 'OUTBOUND')
->in('account_edge_' . $model::collectionName())
->where(['account._id' => yii::$app->user->id])
->select($model::collectionName() . '_edge_account');
if (yii::$app->request->post('last')) {
// Запрос последнего уведомлений (всплывающее окно)
// Уведомление которое не выводилось на мониторе пользователя
$type = 'received';
} else if (yii::$app->request->post('stream')) {
// Запрос последних уведомлений (панель)
// Уведомление которое не было прочитано в окне уведомний
$type = 'checked';
}
// Генерация подзапроса по перебору ядра
$let = $let->createCommand();
/**
* Поиск рёбер: (УВЕДОМЛЕНИЕ)? -> ПОЛЬЗОВАТЕЛЬ
*
* @param bool $check Активация проверки получения
*/
$search = function (bool $check = false) use ($model, $type, $let, $limit): array {
return $model::searchByAccount(
params: $check ? $let->getBindVars() : [],
where: [
[
[
'notification.html' => null
],
'operator' => '!='
],
[
'account_edge_notification.type' => $type
]
],
let: [
$model::collectionName() . '_edge_account',
'(' . (string) $let . ')'
],
post_where: $check ? [
'account_edge_notification[0]._to' => null
] : [],
limit: $limit,
sort: ['DESC'],
direction: 'INBOUND'
);
};
// Поиск непрочитанных уведомлений пользователя
$notifications = $search(true);
if (!yii::$app->request->post('last') && empty($notifications)) {
// Уведомления не найдены и запрошены НЕ всплывающие уведомления
// Поиск уведомлений пользователя
$notifications = $search();
}
if (empty($notifications)) {
// Уведомления не найдены
yii::$app->response->statusCode = 404;
goto end;
}
foreach ($notifications as $notification) {
// Перебор найденных уведомлений
// Запись ребра: ПОЛЬЗОВАТЕЛЬ -> УВЕДОМЛЕНИЕ (о том, что уведомление прочитано)
AccountEdgeNotification::write(yii::$app->user->id, $notification->readId(), $type);
}
if (yii::$app->request->post('last')) {
// Запрос последнего уведомлений (всплывающее окно)
// Реинициализация
$notification = $notifications[0];
$return['popup'] = [
'html' => $this->renderPartial('popup', compact('model', 'notification')),
'id' => 'popup/' . $notification->readId()
];
} else if (yii::$app->request->post('stream')) {
// Запрос последних уведомлений (панель)
$return['panel'] = $this->renderPartial('panel', compact('model', 'notifications'));
}
} else {
// Иначе обрабатывается как запрос страницы уведомлений
$return['main'] = $this->renderPartial('index', compact('model'));
$return['redirect'] = '/notification';
}
end:
return $return;
}
return $this->render('index', ['model' => new Notification(yii::$app->request->get('Notification'))]);
}
}

View File

@ -1,14 +1,17 @@
<?php <?php
declare(strict_types=1);
namespace app\controllers; namespace app\controllers;
use Yii; use Yii;
use yii\filters\AccessControl; use yii\filters\AccessControl;
use yii\web\Controller; use yii\web\Controller;
use yii\web\Response; use yii\web\Response;
use app\models\Product;
use yii\web\HttpException; use yii\web\HttpException;
use app\models\Product;
class ProductController extends Controller class ProductController extends Controller
{ {
/** /**
@ -19,20 +22,24 @@ class ProductController extends Controller
return [ return [
'access' => [ 'access' => [
'class' => AccessControl::class, 'class' => AccessControl::class,
'only' => ['add'],
'rules' => [ 'rules' => [
[
'allow' => true,
'actions' => ['index'],
'roles' => ['@']
],
[ [
'allow' => false, 'allow' => false,
'roles' => ['?'] 'roles' => ['?']
] ],
] ]
] ]
]; ];
} }
public function actionIndex(int $id) public function actionIndex(string $catn)
{ {
if ($model = Product::readById($id)) { if ($model = Product::searchByCatn($catn)) {
// Товар найден // Товар найден
// Инициализация // Инициализация
@ -45,7 +52,7 @@ class ProductController extends Controller
return [ return [
'main' => $this->renderPartial('index', compact('model')), 'main' => $this->renderPartial('index', compact('model')),
'redirect' => '/product/' . $id, 'redirect' => '/product/' . $catn,
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => Yii::$app->request->getCsrfToken()
]; ];
} }

View File

@ -1,9 +1,10 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace app\controllers; namespace app\controllers;
use Yii; use yii;
use yii\filters\AccessControl; use yii\filters\AccessControl;
use yii\web\Controller; use yii\web\Controller;
use yii\web\Response; use yii\web\Response;
@ -12,12 +13,13 @@ use yii\web\UploadedFile;
use app\models\Supply; use app\models\Supply;
use app\models\SupplyGroup; use app\models\SupplyGroup;
use app\models\Search;
use app\models\Notification;
use app\models\Settings;
use app\models\SettingsEdgeSettings;
class ProfileController extends Controller class ProfileController extends Controller
{ {
/**
* {@inheritdoc}
*/
public function behaviors() public function behaviors()
{ {
return [ return [
@ -26,13 +28,20 @@ class ProfileController extends Controller
'rules' => [ 'rules' => [
[ [
'allow' => true, 'allow' => true,
'roles' => ['@'] 'roles' => ['@'],
'actions' => ['index', 'supplies', 'import', 'monitoring', 'readGroups'],
], ],
[ [
'allow' => false, 'allow' => false,
'roles' => ['?'], 'roles' => ['?'],
'verbs' => ['POST'],
'denyCallback' => [$this, 'accessDenied'] 'denyCallback' => [$this, 'accessDenied']
],
[
'allow' => true,
'actions' => ['trusted', 'trusted-notification-write'],
'matchCallback' => function ($rule, $action) {
return yii::$app->user->identity->trst;
}
] ]
] ]
] ]
@ -41,21 +50,32 @@ class ProfileController extends Controller
public function accessDenied() public function accessDenied()
{ {
$cookies = Yii::$app->response->cookies; // Инициализация
$cookies = yii::$app->response->cookies;
// Запись cookie с редиректом, который выполнится после авторизации
$cookies->add(new Cookie([ $cookies->add(new Cookie([
'name' => 'redirect', 'name' => 'redirect',
'value' => Yii::$app->request->pathInfo 'value' => yii::$app->request->pathInfo
])); ]));
Yii::$app->response->format = Response::FORMAT_JSON; if (Yii::$app->request->isPost) {
// POST-запрос
// Проверить переадресацию на уровне сервера // Настройка
Yii::$app->response->content = json_encode([ Yii::$app->response->format = Response::FORMAT_JSON;
'main' => $this->renderPartial('/account'),
'redirect' => '/authentication', // Генерация ответа
'_csrf' => Yii::$app->request->getCsrfToken() Yii::$app->response->content = json_encode([
]); 'main' => $this->renderPartial('/account/index'),
'redirect' => '/authentication',
'_csrf' => Yii::$app->request->getCsrfToken()
]);
} else if (Yii::$app->request->isGet) {
// GET-запрос
$this->redirect('/authentication');
}
} }
/** /**
@ -64,75 +84,236 @@ class ProfileController extends Controller
public function actionIndex(): string|array public function actionIndex(): string|array
{ {
// Инициализация // Инициализация
$model = Yii::$app->user->identity; $model = yii::$app->user->identity;
$attributes = Supply::searchByAccount(yii::$app->user->id);
if ($vars = yii::$app->request->post('Account') ?? yii::$app->request->get('Account')) {
// Обнаружены входные параметры
if (isset($vars['opts'])) {
// Переданы параметры
// Инициализация
is_array($model->opts) || $model->opts = [];
// Запись
$model->opts = array_merge($model->opts, $vars['opts']);
$model->update();
} else {
/**
* @todo Написать обработчик ошибок
*/
}
}
// Генерация // Генерация
$sidebar = $this->renderPartial('sidebar'); $sidebar = $this->renderPartial('sidebar');
if (Yii::$app->request->isAjax) { if (yii::$app->request->isPost) {
// AJAX-POST-запрос // POST-запрос
Yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return [ return [
'main' => $this->renderPartial('index', compact('model', 'sidebar')), 'main' => $this->renderPartial('index', compact('model', 'sidebar', 'attributes')),
'redirect' => '/profile', 'redirect' => '/profile',
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
return $this->render('index', compact('model', 'sidebar')); return $this->render('index', compact('model', 'sidebar', 'attributes'));
} }
/** /**
* Страницка поставок * Страница поставок
*/ */
public function actionSupplies(): string|array public function actionSupplies(): string|array
{ {
// Инициализация // Инициализация
$model = new Supply(Yii::$app->request->post('Supply') ?? Yii::$app->request->get('Supply')); $model = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
// Генерация // Генерация
$sidebar = $this->renderPartial('sidebar', compact('model')); $sidebar = $this->renderPartial('sidebar');
if (Yii::$app->request->isAjax) { if (yii::$app->request->isPost) {
// AJAX-POST-запрос // AJAX-POST-запрос
Yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return [ return [
'main' => $this->renderPartial('supplies', compact('model', 'sidebar')), 'main' => $this->renderPartial('supplies', compact('model', 'sidebar')),
'redirect' => '/profile/supplies', 'redirect' => '/profile/supplies',
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
return $this->render('supplies', compact('model', 'sidebar')); return $this->render('supplies', compact('model', 'sidebar'));
} }
/**
* Страница панели управления для доверенных пользователей
*/
public function actionTrusted(): string|array
{
// Инициализация
$model_notifications = null;
$model_settings = Settings::readLast();
if (!is_null($vars = yii::$app->request->post('Notification') ?? yii::$app->request->get('Notification'))) {
// Обнаружены входные параметры из раздела "Уведомления"
// Реинициализация с новыми параметрами
$model_notifications = new Notification($vars);
// Запись уведомления и отправка (запись ребра до аккаунта)
$model_notifications->write();
} else if (!is_null($vars = yii::$app->request->post('Settings') ?? yii::$app->request->get('Settings'))) {
// Обнаружены входные параметры из раздела "Настройки"
if ($to = new Settings($vars)) {
// Настройки инициализированы
// Отправка
if ($to->save()) {
// Сохранено в базе данных
// Буфер
$from = $model_settings;
// Реинициализация (для представления)
$model_settings = $to;
if ($from) {
// Найдена старая версия настроек
// Запись ребра: НАСТРОЙКИ (старые) -> НАСТРОЙКИ (новые)
SettingsEdgeSettings::write($from->readId(), $to->readId(), 'update');
}
} else {
// Не сохранено в базе данных
// Запись ошибок
$model_settings->addErrors($to->getErrors());
}
}
}
// Деинициализация
unset($vars);
// Генерация
$sidebar = $this->renderPartial('sidebar');
if (yii::$app->request->isPost) {
// AJAX-POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return [
'main' => $this->renderPartial('trusted', compact('model_notifications', 'model_settings', 'sidebar')),
'redirect' => '/profile/trusted',
'_csrf' => yii::$app->request->getCsrfToken()
];
}
return $this->render('trusted', compact('model_notifications', 'model_settings', 'sidebar'));
}
/**
* Страницка панели управления для доверенных пользователей
*
* @todo Перенести в уведомления
*/
// public function actionTrustedNotificationWrite(): string|array
// {
// // Инициализация
// $model = new Notification(yii::$app->request->post('Notification') ?? yii::$app->request->get('Notification'));
// $model->write();
// if (yii::$app->request->isPost) {
// // POST-запрос
// yii::$app->response->format = Response::FORMAT_JSON;
// return [
// 'main' => $this->renderPartial('trusted', compact('model', 'sidebar')),
// 'redirect' => '/profile/trusted',
// '_csrf' => yii::$app->request->getCsrfToken()
// ];
// }
// return $this->render('trusted', compact('model', 'sidebar'));
// }
/**
* Страницка панели управления для доверенных пользователей
*/
public function actionMonitoring(): string|array
{
// Инициализация номера страницы
$page_search_history = (yii::$app->request->post('search') ?? yii::$app->request->get('search')) - 1;
if ($page_search_history <= 0) {
$page_search_history = 0;
}
// Инициализация количества строк на одной странице
$rows_amount = 10;
// Генерация
$sidebar = $this->renderPartial('sidebar');
$search_history = Search::searchByAccount(yii::$app->user->id, $rows_amount, ((int) $page_search_history ?? 0) * $rows_amount);
if (yii::$app->request->isPost) {
// AJAX-POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return [
'main' => $this->renderPartial('monitoring', compact(
'sidebar',
'search_history',
'page_search_history'
)),
'search' => $page_search_history + 1,
'redirect' => '/profile/monitoring',
'_csrf' => yii::$app->request->getCsrfToken()
];
}
return $this->render('monitoring', compact(
'sidebar',
'search_history',
'page_search_history'
));
}
public function actionImport() public function actionImport()
{ {
// Инициализация // Инициализация
$model = new Supply(Yii::$app->request->post('Supply') ?? Yii::$app->request->get('Supply')); $model = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
$model->scenario = $model::SCENARIO_IMPORT; $model->scenario = $model::SCENARIO_IMPORT;
// Генерация // Генерация
$sidebar = $this->renderPartial('sidebar', compact('model')); $sidebar = $this->renderPartial('sidebar');
if (Yii::$app->request->isAjax) { if (yii::$app->request->isPost) {
// AJAX-POST-запрос // AJAX-POST-запрос
Yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
$model->file = UploadedFile::getInstances($model, 'file'); $model->file = UploadedFile::getInstances($model, 'file');
if (!$test = $model->import()) { if (!$test = $model->import()) {
Yii::$app->response->statusCode = 409; yii::$app->response->statusCode = 409;
} }
return [ return [
'main' => $this->renderPartial('supplies', compact('model', 'sidebar')), 'main' => $this->renderPartial('supplies', compact('model', 'sidebar')),
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }

View File

@ -1,19 +1,18 @@
<?php <?php
declare(strict_types=1);
namespace app\controllers; namespace app\controllers;
use Yii; use yii;
use yii\filters\AccessControl; use yii\filters\AccessControl;
use yii\web\Controller; use yii\web\Controller;
use yii\web\Response; use yii\web\Response;
use app\models\AccountForm; use app\models\AccountForm;
class RegistrationController extends Controller class RegistrationController extends Controller
{ {
/**
* {@inheritdoc}
*/
public function behaviors() public function behaviors()
{ {
return [ return [
@ -31,16 +30,16 @@ class RegistrationController extends Controller
public function actionIndex() public function actionIndex()
{ {
if (Yii::$app->request->isAjax) { if (yii::$app->request->isPost) {
// AJAX-POST-запрос // POST-запрос
// Инициализация // Инициализация
$model = new AccountForm(Yii::$app->request->post('AccountForm')); $model = new AccountForm(yii::$app->request->post('AccountForm'));
$model->scenario = $model::SCENARIO_REGISTRATION; $model->scenario = $model::SCENARIO_REGISTRATION;
Yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
if (!Yii::$app->user->isGuest || $model->registration()) { if (!yii::$app->user->isGuest || $model->registration()) {
// Данные прошли проверку и аккаунт был создан // Данные прошли проверку и аккаунт был создан
// Аутентификация // Аутентификация
@ -49,11 +48,11 @@ class RegistrationController extends Controller
// Запись ответа // Запись ответа
$return = [ $return = [
'menu' => (new AccountForm())->deauthenticationGenHtml(), 'menu' => $this->renderPartial('/account/deauthentication'),
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
if (($cookies = Yii::$app->response->cookies)->has('redirect')) { if (($cookies = yii::$app->response->cookies)->has('redirect')) {
// Найдено cookie с переадресацией // Найдено cookie с переадресацией
// Запись ответа // Запись ответа
@ -61,11 +60,11 @@ class RegistrationController extends Controller
$return['main'] = $this->renderPartial($return['redirect'] . '/index'); $return['main'] = $this->renderPartial($return['redirect'] . '/index');
// Очистка cookie // Очистка cookie
unset(Yii::$app->response->cookies['redirect']); unset(yii::$app->response->cookies['redirect']);
} else { } else {
// Не найдено cookie с переадресацией // Не найдено cookie с переадресацией
if (Yii::$app->request->pathInfo === 'authentication' || Yii::$app->request->pathInfo === 'registration') { if (yii::$app->request->pathInfo === 'authentication' || yii::$app->request->pathInfo === 'registration') {
// Если клиент на промежуточном URI // Если клиент на промежуточном URI
// Запись ответа // Запись ответа
@ -78,18 +77,18 @@ class RegistrationController extends Controller
} else { } else {
// Данные не прошли проверку // Данные не прошли проверку
Yii::$app->response->statusCode = 400; yii::$app->response->statusCode = 400;
return [ return [
'main' => $this->renderPartial('/account', compact('model')), 'main' => $this->renderPartial('/account', compact('model')),
'redirect' => '/registration', 'redirect' => '/registration',
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
} }
if (!Yii::$app->user->isGuest) { if (!yii::$app->user->isGuest) {
Yii::$app->response->redirect('/'); yii::$app->response->redirect('/');
} else { } else {
return $this->render('/account', compact('model')); return $this->render('/account', compact('model'));
} }

View File

@ -1,45 +1,90 @@
<?php <?php
declare(strict_types=1);
namespace app\controllers; namespace app\controllers;
use Yii; use yii;
use yii\web\Controller; use yii\web\Controller;
use yii\web\Response; use yii\web\Response;
use app\models\Product; use app\models\Product;
use yii\web\Cookie; use app\models\Search;
class SearchController extends Controller class SearchController extends Controller
{ {
/**
* @todo Сессию привязать к аккаунту и проверку по нему делать, иначе её можно просто сбрасывать
*/
public function actionIndex(): array|string public function actionIndex(): array|string
{ {
// Инициализация // Инициализация
$query = Yii::$app->request->post('request') ?? Yii::$app->request->get('q'); $query = yii::$app->request->post('request') ?? yii::$app->request->get('q');
if (Yii::$app->request->post('type') === 'product' || Yii::$app->request->get('type') === 'product') { if (yii::$app->request->post('type') === 'product' || yii::$app->request->get('type') === 'product') {
// Поиск по продуктам // Поиск по продуктам
if (yii::$app->request->post('history')) {
// Запрошена история
yii::$app->response->format = Response::FORMAT_JSON;
return [
'search_line_window' => $this->renderPartial('/search/panel', ['history' => true]),
'_csrf' => yii::$app->request->getCsrfToken()
];
}
// Инициализация сессии // Инициализация сессии
$session = Yii::$app->session; $session = yii::$app->session;
$session->open(); $session->open();
// Инициализация ответа // Инициализация ответа
$response = null; $response = null;
// Инициализация параметров
$timer = 0;
// Период пропуска запросов (в секундах) // Период пропуска запросов (в секундах)
$period = 1; $period = 3;
$keep_connect = true;
$sanction = false;
$sanction_condition = ($session['last_request'] + $period - time()) < $period;
$sanction_time = 2;
$query_min = 2;
$query_max = 20;
if (isset($session['last_request'])) {
// Данные о времени последнего запроса не найдены
if ($sanction && $sanction_condition) {
// Наказание за повторный запрос при условии задержки
$session['last_request'] += $sanction_time;
}
} else {
// Это первый запрос
// Инициализация
$session['last_request'] = time();
goto first_request;
}
keep_connect_wait:
// Запись времени последнего запроса и вычисление об истечении таймера // Запись времени последнего запроса и вычисление об истечении таймера
$timer = ($session['last_request'] ?? $session['last_request'] = time() + $period) - time(); $timer = $session['last_request'] + $period - time();
if ($timer > 0) { if ($timer > 0) {
// Ожидание перед повторным запросом при условии что старых запросов нет // Ожидание перед повторным запросом при условии что старых запросов нет
Yii::$app->response->statusCode = 202; yii::$app->response->statusCode = 202;
$return = [ $return = [
'timer' => $timer, 'timer' => $timer,
'search_line_window' => $this->renderPartial('/loading'), 'search_line_window' => $this->renderPartial('/loading'),
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} else { } else {
// Повторный запрос по истечению ожидания // Повторный запрос по истечению ожидания
@ -47,22 +92,33 @@ class SearchController extends Controller
// Очистка времени последнего запроса // Очистка времени последнего запроса
unset($session['last_request']); unset($session['last_request']);
// first_request:
// Здесь запись истории запросов (в базе данных)
//
$limit = Yii::$app->request->isAjax ? 10 : 30; if (strlen($query) < $query_min) {
// Выход за ограничения длины с недостатком
if ($response = Product::searchByCatn($query, $limit)) { goto skip_query;
} else if (strlen($query) > $query_max) {
// Выход за ограничения длины с превышением
goto skip_query;
}
// Запись в историю
Search::write($query);
$limit = yii::$app->request->isPost ? 10 : 20;
if ($response = Product::searchByCatn($query, $limit, [])) {
// Данные найдены по поиску в полях Каталожного номера // Данные найдены по поиску в полях Каталожного номера
// Запись ответа // Запись ответа
$return = [ $return = [
'search_line_window' => $this->renderPartial('/search/panel', compact('response')), 'search_line_window' => $this->renderPartial('/search/panel', compact('response')),
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
if ((int) Yii::$app->request->post('advanced')) { if ((int) yii::$app->request->post('advanced')) {
// Полноценный поиск // Полноценный поиск
// Запись ответа // Запись ответа
@ -71,30 +127,40 @@ class SearchController extends Controller
$return['redirect'] = '/search?type=product&q=' . $query; $return['redirect'] = '/search?type=product&q=' . $query;
} }
} else { } else {
// Данные не найдены // Данные не найдены
Yii::$app->response->statusCode = 404; yii::$app->response->statusCode = 404;
} }
} }
if (Yii::$app->request->isAjax) { skip_query:
// AJAX-POST-запрос
Yii::$app->response->format = Response::FORMAT_JSON; if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return ?? [ return $return ?? [
'search_line_window' => $this->renderPartial('/search/panel'), 'search_line_window' => $this->renderPartial('/search/panel'),
'_csrf' => Yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} else { } else {
// GET-запрос // GET-запрос
if (empty($return['main']) && $keep_connect && $timer > 0) {
// Режим непрерывного соединения
// Ожидание
sleep($timer);
goto keep_connect_wait;
}
return $this->render('/search/index', compact('response', 'timer')); return $this->render('/search/index', compact('response', 'timer'));
} }
} }
if (Yii::$app->user->isGuest) { if (yii::$app->user->isGuest) {
return $this->render('/search/index', ['error_auth' => true]); return $this->render('/search/index', ['error_auth' => true]);
} else { } else {
return $this->render('/search/index'); return $this->render('/search/index');

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m201219_074926_create_account_collection extends Migration class m201219_074926_create_account_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210101_092505_create_product_collection extends Migration class m210101_092505_create_product_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210107_163448_create_supply_collection extends Migration class m210107_163448_create_supply_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210108_014505_create_account_edge_supply_collection extends Migration class m210108_014505_create_account_edge_supply_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210108_212826_create_product_group_collection extends Migration class m210108_212826_create_product_group_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210108_221446_create_product_edge_product_group_collection extends Migration class m210108_221446_create_product_edge_product_group_collection extends Migration
{ {

View File

@ -1,16 +1,16 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210108_222132_create_supply_edge_product_collection extends Migration class m210108_222132_create_supply_edge_product_collection extends Migration
{ {
public function up() public function up()
{ {
$this->createCollection('supply_edge_product', ['Type' => 3]); $this->createCollection('supply_edge_product', ['Type' => 3]);
} }
public function down() public function down()
{ {
$this->dropCollection('supply_edge_product'); $this->dropCollection('supply_edge_product');
} }
} }

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210108_222740_create_product_edge_product_collection extends Migration class m210108_222740_create_product_edge_product_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210109_214817_create_supply_group_collection extends Migration class m210109_214817_create_supply_group_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210109_214833_create_supply_edge_supply_group_collection extends Migration class m210109_214833_create_supply_edge_supply_group_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210111_044635_create_supply_edge_supply_collection extends Migration class m210111_044635_create_supply_edge_supply_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210112_010347_create_product_group_edge_product_group_collection extends Migration class m210112_010347_create_product_group_edge_product_group_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210112_010411_create_supply_group_edge_supply_group_collection extends Migration class m210112_010411_create_supply_group_edge_supply_group_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210112_034135_create_requisite_collection extends Migration class m210112_034135_create_requisite_collection extends Migration
{ {

View File

@ -1,12 +1,12 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210112_034232_create_supply_edge_requisite_collection extends Migration class m210112_034232_create_supply_edge_requisite_collection extends Migration
{ {
public function up() public function up()
{ {
$this->createCollection('supply_edge_requisite', ['Type' => 3]); $this->createCollection('supply_edge_requisite', ['Type' => 3]);
} }
public function down() public function down()

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210113_021800_create_purchase_collection extends Migration class m210113_021800_create_purchase_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210113_021905_create_account_edge_purchase_collection extends Migration class m210113_021905_create_account_edge_purchase_collection extends Migration
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
use explosivebit\arangodb\Migration; use mirzaev\yii2\arangodb\Migration;
class m210113_021917_create_purchase_edge_supply_collection extends Migration class m210113_021917_create_purchase_edge_supply_collection extends Migration
{ {

View File

@ -0,0 +1,16 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m210206_154140_create_search_collection extends Migration
{
public function up()
{
$this->createCollection('search', []);
}
public function down()
{
$this->dropCollection('search');
}
}

View File

@ -0,0 +1,16 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m210206_154210_create_account_edge_search_collection extends Migration
{
public function up()
{
$this->createCollection('account_edge_search', ['Type' => 3]);
}
public function down()
{
$this->dropCollection('account_edge_search');
}
}

View File

@ -0,0 +1,16 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m210209_185314_create_notification_collection extends Migration
{
public function up()
{
$this->createCollection('notification', []);
}
public function down()
{
$this->dropCollection('notification');
}
}

View File

@ -0,0 +1,16 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m210209_185328_create_account_edge_notification_collection extends Migration
{
public function up()
{
$this->createCollection('account_edge_notification', ['type' => 3]);
}
public function down()
{
$this->dropCollection('account_edge_notification');
}
}

View File

@ -0,0 +1,16 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m210223_121142_create_settings_collection extends Migration
{
public function up()
{
$this->createCollection('settings', []);
}
public function down()
{
$this->dropCollection('settings');
}
}

View File

@ -0,0 +1,16 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m210223_145042_create_settings_edge_settings_collection extends Migration
{
public function up()
{
$this->createCollection('settings_edge_settings', ['type' => 3]);
}
public function down()
{
$this->dropCollection('settings_edge_settings');
}
}

View File

@ -16,8 +16,6 @@ use carono\exchange1c\interfaces\PartnerInterface;
*/ */
class Account extends Document implements IdentityInterface, PartnerInterface class Account extends Document implements IdentityInterface, PartnerInterface
{ {
public $opts;
/** /**
* Имя коллекции * Имя коллекции
*/ */
@ -43,7 +41,8 @@ class Account extends Document implements IdentityInterface, PartnerInterface
'comp', 'comp',
'taxn', 'taxn',
'onec', 'onec',
'opts' 'opts',
'trst'
] ]
); );
} }
@ -65,7 +64,8 @@ class Account extends Document implements IdentityInterface, PartnerInterface
'comp' => 'Компания', 'comp' => 'Компания',
'taxn' => 'ИНН', 'taxn' => 'ИНН',
'onec' => 'Данные 1C', 'onec' => 'Данные 1C',
'opts' => 'Параметры' 'opts' => 'Параметры',
'trst' => 'Доверенный пользователь'
] ]
); );
} }

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace app\models;
class AccountEdgeNotification extends Edge
{
public static function collectionName(): string
{
return 'account_edge_notification';
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace app\models;
class AccountEdgeSearch extends Edge
{
public static function collectionName(): string
{
return 'account_edge_search';
}
}

View File

@ -135,38 +135,4 @@ class AccountForm extends Model
return $this->account; return $this->account;
} }
public function authenticationGenHtml(string $dropdown): string
{
return <<<HTML
<a class="text-dark my-auto mr-2" href="/cart"><i class="fas fa-shopping-cart mx-2"></i></a>
<a class="text-dark my-auto mr-2" href="/orders"><i class="fas fa-list mx-2"></i></a>
<div class="btn-group">
<a class="btn m-0 px-0 text-dark button_clean" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();">Личный кабинет</a>
<button id="profile_button" class="btn pr-0 dropdown-toggle dropdown-toggle-split button_clean" type="button" data-toggle="dropdown" onmouseover="$('#profile_button').dropdown('show')"></button>
<div class="dropdown-menu dropdown-menu-long dropdown-menu-right p-3" aria-labelledby="profile_button" onmouseout="$('#profile_button').dropdown('show')">
<h5 class="mb-3 text-center">Аутентификация</h5>
$dropdown
<!-- <a class="dropdown-item-text text-center px-0 py-2" href="#"><small>Восстановление пароля</small></a> -->
</div>
</div>
HTML;
}
public function deauthenticationGenHtml(): string
{
$mail = Yii::$app->user->identity->mail;
return <<<HTML
<a class="text-dark my-auto mr-2" href="/cart"><i class="fas fa-shopping-cart mx-2"></i></a>
<a class="text-dark my-auto mr-2" href="/orders"><i class="fas fa-list mx-2"></i></a>
<div class="btn-group">
<a class="btn m-0 px-0 text-dark button_clean" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();">Личный кабинет</a>
<button id="profile_button" class="btn pr-0 dropdown-toggle dropdown-toggle-split button_clean" type="button" data-toggle="dropdown" onmouseover="$('#profile_button').dropdown('show')"></button>
<div class="dropdown-menu dropdown-menu-right py-1" aria-labelledby="profile_button" onmouseout="$('#profile_button').dropdown('show')">
<a class="dropdown-item button_white text-dark" onclick="deauthentication()">Выход ($mail)</a>
</div>
</div>
HTML;
}
} }

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace app\models; namespace app\models;
use Yii; use yii;
use mirzaev\yii2\arangodb\ActiveRecord; use mirzaev\yii2\arangodb\ActiveRecord;
@ -31,7 +31,7 @@ abstract class Document extends ActiveRecord
return [ return [
'_key', '_key',
'date', 'date',
'writer' 'wrtr'
]; ];
} }
@ -41,8 +41,9 @@ abstract class Document extends ActiveRecord
public function attributeLabels(): array public function attributeLabels(): array
{ {
return [ return [
'_key' => 'Ключ',
'date' => 'Дата', 'date' => 'Дата',
'writer' => 'Аккаунт записавшего' 'wrtr' => 'Аккаунт записавшего'
]; ];
} }
@ -56,33 +57,22 @@ abstract class Document extends ActiveRecord
{ {
return [ return [
[ [
'writer', 'wrtr',
'string' 'string'
],
[
'wrtr',
'default',
'value' => yii::$app->user->id
],
[
'date',
'default',
'value' => time()
] ]
]; ];
} }
/**
* Перед сохранением
*
* @todo Подождать обновление от ебаного Yii2 и добавить
* проверку типов передаваемых параметров
*/
public function beforeSave($data): bool
{
if (parent::beforeSave($data)) {
if ($this->isNewRecord) {
}
$this->date = time();
$this->writer = $this->writer ?? Yii::$app->user->id;
return true;
}
return false;
}
/** /**
* Чтение идентификатора * Чтение идентификатора
*/ */
@ -99,6 +89,19 @@ abstract class Document extends ActiveRecord
return static::findOne(['_id' => $_id]); return static::findOne(['_id' => $_id]);
} }
public static function readLast(): ?static
{
return static::find()->orderBy(['DESC'])->one();
}
/**
* Чтение всех записей
*/
public static function readAll(): array
{
return static::find()->all();
}
/** /**
* Чтение количества записей * Чтение количества записей
*/ */
@ -106,4 +109,16 @@ abstract class Document extends ActiveRecord
{ {
return static::find()->count(); return static::find()->count();
} }
/**
* Проверка на то, что в свойство передан массив
*/
protected function arrayValidator(string $attribute, array $params): bool
{
if (is_array($this->$attribute)) {
return true;
}
return false;
}
} }

View File

@ -60,38 +60,6 @@ abstract class Edge extends Document
); );
} }
/**
* Записать
*/
public static function write(string $_from, string $_to, string $type = '', array $data = []): ?static
{
// Инициализация
$edge = new static;
// Настройка
$edge->_from = $_from;
$edge->_to = $_to;
$edge->type = $type;
foreach ($data as $key => $value) {
if(is_int($key)) {
// Если ключ задан автоматически
$edge->{$value} = true;
} else {
// Иначе ключ записан вручную
$edge->{$key} = $value;
}
}
// Запись
$edge->save();
return $edge;
}
/** /**
* Перед сохранением * Перед сохранением
* *
@ -111,4 +79,63 @@ abstract class Edge extends Document
return false; return false;
} }
/**
* Записать (с проверкой на существование)
*
* Создаст ребро только в том случае, если его аналога не существует
*/
public static function writeSafe(string $_from, string $_to, string $type = '', array $data = []): ?static
{
if ($edge = self::searchByVertex($_from, $_to, 1)) {
// Найдено в базе данных
return $edge;
}
return self::write($_from, $_to, $type, $data);
}
/**
* Записать
*/
public static function write(string $_from, string $_to, string $type = '', array $data = []): ?static
{
// Инициализация
$edge = new static;
// Настройка
$edge->_from = $_from;
$edge->_to = $_to;
$edge->type = $type;
foreach ($data as $key => $value) {
if (is_int($key)) {
// Обычная запись
$edge->{$value} = true;
} else {
// Ассоциативная запись
$edge->{$key} = $value;
}
}
// Запись
return $edge->save() ? $edge : null;
}
/**
* Поиск ребра по его вершинам
*/
public static function searchByVertex(string $_from, string $_to, int $limit = 1): static|array|null
{
$query = self::find()->where(['_from' => $_from, '_to' => $_to]);
if ($limit <= 1) {
return $query->one();
} else {
return $query->limit($limit)->all();
}
}
} }

View File

@ -0,0 +1,226 @@
<?php
declare(strict_types=1);
namespace app\models;
use yii;
use yii\web\IdentityInterface;
use app\models\traits\SearchByAccount;
/**
* Поиск
*
* @see Product Поиск по товарам
*/
class Notification extends Document
{
use SearchByAccount;
/**
* Сценарий для доверенного пользователя с созданием уведомления
*/
const SCENARIO_TRUSTED_CREATE = 'create';
/**
* Цель для отправки уведомления
*
* Расшифровывается как $target
*
* @see SCENARIO_TRUSTED_CREATE
*/
public IdentityInterface|string|array|null $trgt;
/**
* Текст уведомления
*/
public string $text;
/**
* Типы уведомлений
*/
public array $typs = [
'notice' => 'Уведомление',
'warning' => 'Предупреждение'
];
/**
* Имя коллекции
*/
public static function collectionName(): string
{
return 'notification';
}
/**
* Свойства
*/
public function attributes(): array
{
return array_merge(
parent::attributes(),
[
'html',
'type'
]
);
}
/**
* Метки свойств
*/
public function attributeLabels(): array
{
return array_merge(
parent::attributeLabels(),
[
'html' => 'HTML',
'type' => 'Тип',
'trgt' => 'Получатели',
'text' => 'Текст',
]
);
}
/**
* Правила
*/
public function rules(): array
{
return array_merge(
parent::rules(),
[
[
'html',
'required'
],
[
'type',
'default',
'value' => 'notice'
]
]
);
}
/**
* Запись
*
* @param string $html HTML уведомления
* @param IdentityInterface $trgt Получатель уведомления
* @param string $type Тип уведомления
*
* @todo Создать параметр разделителя для администрации
*/
public function write(string $html = null, IdentityInterface|array|string $trgt = null, string $type = 'notice'): self|array|null
{
// Инициализация
isset($this->html) ? $html = $this->html : null;
isset($this->trgt) ? $trgt = $this->trgt : null;
isset($this->type) ? $type = $this->type : null;
// Инициализация уведомления
if (isset($html) && (bool) (int) $html) {
// Получен текст в формете HTML-кода
$this->html = $this->text ?? null;
} else {
// Получен необработанный текст
$text = htmlspecialchars(strip_tags($this->text ?? null));
$this->html = <<<HTML
<p class="my-2 mx-3">$text</p>
HTML;
}
if ($this->save()) {
// Уведомление записано
// Инициализация получателей и создание ребра
if (empty($trgt)) {
// Получатель не передан
goto test;
} else if (is_string($trgt)) {
// Передана необработанная строка
// Инициализация
$delimiter = ',';
// Конвертация
$trgt = array_map('trim', explode($delimiter, $trgt));
if (in_array('@all', $trgt, true)) {
// Найден флаг обозначающий отправку всем пользователям
// Инициализация
$return = [];
foreach (Account::readAll() as $target) {
// Перебор всех аккаунтов
// Запись ребра: УВЕДОМЛЕНИЕ -> АККАУНТ
$return[] = AccountEdgeNotification::writeSafe($this->readId(), $target->readId(), $type) ? $this : null;
}
return $return ? $return : null;
}
if (in_array('@test', $trgt, true)) {
// Найден флаг обозначающий тестирование (отправка самому себе)
test:
return AccountEdgeNotification::writeSafe($this->readId(), yii::$app->user->id, $type) ? $this : null;
}
}
if (is_array($trgt)) {
// Несколько получателей
// Инициализация
$return = [];
foreach ($trgt as $target) {
// Перебор получателей
if ($target instanceof Account) {
// Один получатель
// Запись ребра: УВЕДОМЛЕНИЕ -> АККАУНТ
return AccountEdgeNotification::writeSafe($this->readId(), $target->readId(), $type) ? $this : null;
}
// "or" имеет приоритет ниже чем у "||" относительно "="
//
// if ($target = $buffer = Account::searchById($target) or
// $target = Account::searchById(Account::collectionName() . '/' . $buffer)
// ) {
//
// if (($target = Account::searchById($target)) ||
// ($target = Account::searchById(Account::collectionName() . '/' . $target))
// ) {
if ($target = Account::searchById(Account::collectionName() . '/' . $target)) {
// Аккаунт найден
echo ($target->readId()) . "\n";
// Запись ребра: УВЕДОМЛЕНИЕ -> АККАУНТ
$return[] = AccountEdgeNotification::writeSafe($this->readId(), $target->readId(), $type) ? $this : null;
}
}
return $return ? $return : null;
} else if ($trgt instanceof Account) {
// Один получатель
// Запись ребра: УВЕДОМЛЕНИЕ -> АККАУНТ
return AccountEdgeNotification::writeSafe($this->readId(), $trgt->readId(), $type) ? $this : null;
}
}
return null;
}
}

View File

@ -54,6 +54,7 @@ class Product extends Document
'name', 'name',
'ocid', 'ocid',
'catn', 'catn',
'imgs',
'oemn' 'oemn'
// 'data', // 'data',
// 'cost', // 'cost',
@ -73,6 +74,7 @@ class Product extends Document
'name' => 'Название (name)', 'name' => 'Название (name)',
'ocid' => 'Идентификатор 1C (ocid)', 'ocid' => 'Идентификатор 1C (ocid)',
'catn' => 'Каталожный номер (catn)', 'catn' => 'Каталожный номер (catn)',
'imgs' => 'Изображения (imgs)',
'oemn' => 'OEM номера (oemn)', 'oemn' => 'OEM номера (oemn)',
// 'data' => 'Данные товара (data)', // 'data' => 'Данные товара (data)',
// 'cost' => 'Цены (cost)', // 'cost' => 'Цены (cost)',
@ -92,7 +94,10 @@ class Product extends Document
parent::rules(), parent::rules(),
[ [
[ [
['name', 'catn'], [
'name',
'catn'
],
'required', 'required',
'message' => 'Заполните поля: {attribute}', 'message' => 'Заполните поля: {attribute}',
'on' => self::SCENARIO_WRITE, 'on' => self::SCENARIO_WRITE,
@ -104,8 +109,19 @@ class Product extends Document
'message' => 'Заполните поля: {attribute}', 'message' => 'Заполните поля: {attribute}',
'on' => self::SCENARIO_IMPORT 'on' => self::SCENARIO_IMPORT
], ],
['catn', 'string', 'message' => '{attribute} должен быть строкой'], [
// ['oemn', 'integer'], Нужна своя проверка на массив 'catn',
'string',
'message' => '{attribute} должен быть строкой'
],
[
[
'oemn',
'imgs'
],
'arrayValidator',
'message' => '{attribute} должен быть массивом.'
],
[ [
'file', 'file',
'file', 'file',
@ -122,6 +138,86 @@ class Product extends Document
); );
} }
/**
* Запись
*
* @param string $catn Артикул, каталожный номер
*/
public static function initEmpty(string $catn): self|array
{
$oemn = self::convertOemn2Catn($catn);
if (count($oemn) === 1) {
// Передан только один артикул
if ($model = self::searchByCatn($catn)) {
// Продукт уже существует
return $model;
}
// Запись
return self::writeEmpty($catn);
}
// Инициализация
$models = [];
foreach ($oemn as $catn) {
// Перебор всех найденных артикулов
if ($model = self::searchByCatn($catn)) {
// Продукт уже существует
continue;
}
// Запись
if ($model = self::writeEmpty($catn)) {
// Записано
// Запись в массив сохранённых моделей
$models[] = $model;
}
}
return $models;
}
/**
* Запись
*/
public static function writeEmpty(string $catn): ?self
{
// Инициализация
$model = new self;
// Настройки
$model->catn = $catn;
// Запись
return $model->save() ? $model : null;
}
/**
* Поиск OEM номеров
*
* @param string $oemn OEM номера
* @param string $delimiters Разделители
*
* @todo НЕ ЗАБЫТЬ СДЕЛАТЬ НАСТРОЙКУ РАЗДЕЛИТЕЛЕЙ
*/
public static function convertOemn2Catn(string $oemn, string $delimiters = '\s\+\/,'): array
{
// Инициализация
$catn = [];
// Конвертация
preg_match_all("/[^$delimiters]+/", $oemn, $catn);
return $catn[0];
}
/** /**
* Импорт товаров * Импорт товаров
* *
@ -182,13 +278,33 @@ class Product extends Document
/** /**
* Поиск по каталожному номеру * Поиск по каталожному номеру
*
* Ищет продукт и возвращает его,
* либо выполняет поиск через представление
*
* @todo Переделать нормально
*/ */
public static function searchByCatn(string $query, int $limit = 1): Product|array|null public static function searchByCatn(string $catn, int $limit = 1, array $select = ['catn' => 'catn']): static|array|null
{ {
if ($limit <= 1) { if ($limit <= 1) {
return static::findOne(['catn' => $query]); return static::findOne(['catn' => $catn]);
} }
return self::find()->limit($limit)->view('product_search', ['id' => '_key', 'catn' => 'catn'], ['catn' => $query], 'START'); $query = self::find()
->collection('product_search')
->search(['catn' => $catn])
->limit($limit)
->select($select)
->createCommand()
->execute()
->getAll();
foreach ($query as &$attribute) {
// Приведение всех свойств в массив и очистка от лишних данных
$attribute = $attribute->getAll();
}
return $query;
} }
} }

View File

@ -154,11 +154,6 @@ class ProductGroup extends Document implements GroupInterface
return $edge->save(); return $edge->save();
} }
public static function readAll()
{
return static::find()->all();
}
public static function readByName(string $name) public static function readByName(string $name)
{ {
return static::findOne(['name' => $name]); return static::findOne(['name' => $name]);

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace app\models;
use yii;
use yii\web\IdentityInterface;
use app\models\traits\SearchByAccount;
/**
* Поиск
*
* @see Product Поиск по товарам
*/
class Search extends Document
{
use SearchByAccount;
/**
* Имя коллекции
*/
public static function collectionName(): string
{
return 'search';
}
/**
* Свойства
*/
public function attributes(): array
{
return array_merge(
parent::attributes(),
[
'text',
'ipv4',
'head'
]
);
}
/**
* Метки свойств
*/
public function attributeLabels(): array
{
return array_merge(
parent::attributeLabels(),
[
'text' => 'Текст',
'ipv4' => 'IPv4',
'head' => 'Заголовки'
]
);
}
/**
* Правила
*/
public function rules(): array
{
return array_merge(
parent::rules(),
[
[
'text',
'required'
],
[
'ipv4',
'default',
'value' => yii::$app->request->userIP
],
[
'head',
'default',
'value' => yii::$app->request->getHeaders()
]
]
);
}
/**
* Запись
*
* @param string $text Текст запроса
* @param IdentityInterface|null $user Пользователь совершивший запрос
*/
public static function write(string $text, IdentityInterface $user = null): ?self
{
// Инициализация
$vertex = new self;
isset($user) && yii::$app->user->isGuest ?: $user = yii::$app->user->identity;
// Настройки
$vertex->text = $text;
if ($vertex->save()) {
// Поиск записан
// Запись ребра: АККАУНТ -> ПОИСК
return $user && AccountEdgeSearch::writeSafe($user->id, $vertex->readId(), 'request') ? $vertex : null;
}
return null;
}
}

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace app\models;
/**
* Настройки
*/
class Settings extends Document
{
/**
* Имя коллекции
*/
public static function collectionName(): string
{
return 'settings';
}
/**
* Свойства
*/
public function attributes(): array
{
return array_merge(
parent::attributes(),
[
'search_period',
'search_connect_keep'
]
);
}
/**
* Метки свойств
*/
public function attributeLabels(): array
{
return array_merge(
parent::attributeLabels(),
[
'search_period' => 'Поисковый период',
'search_connect_keep' => 'Режим удержания'
]
);
}
/**
* Правила
*/
public function rules(): array
{
return array_merge(
parent::rules(),
[
[
[
'search_period'
],
'integer',
'message' => '{attribute} должен хранить цифровое значение'
],
[
[
'search_connect_keep'
],
'string',
'message' => '{attribute} должен хранить строковый тип'
]
]
);
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace app\models;
class SettingsEdgeSettings extends Edge
{
public static function collectionName(): string
{
return 'settings_edge_settings';
}
}

View File

@ -12,6 +12,8 @@ use app\models\traits\Xml2Array;
use carono\exchange1c\interfaces\ProductInterface; use carono\exchange1c\interfaces\ProductInterface;
use exception;
/** /**
* Поставка (выгрузка товаров от поставщиков) * Поставка (выгрузка товаров от поставщиков)
* *
@ -66,8 +68,15 @@ class Supply extends Product implements ProductInterface
*/ */
public function afterSave($data, $vars): void public function afterSave($data, $vars): void
{ {
// Запись ребра: АККАУНТ -> ПОСТАВКА if (AccountEdgeSupply::searchByVertex(Yii::$app->user->id, $this->readId())) {
(new AccountEdgeSupply)->write(Yii::$app->user->id, $this->readId(), 'import'); // Ребро уже существует
} else {
// Ребра не существует
// Запись ребра: АККАУНТ -> ПОСТАВКА
(new AccountEdgeSupply)->write(Yii::$app->user->id, $this->readId(), 'import');
}
} }
/** /**
@ -97,31 +106,25 @@ class Supply extends Product implements ProductInterface
* *
* Ищет записанные свойства из 1C по их идентификатору и добавляет к ним * Ищет записанные свойства из 1C по их идентификатору и добавляет к ним
* недостающие данные. Это костыль оставшийся от реляционных баз данных * недостающие данные. Это костыль оставшийся от реляционных баз данных
*
* @todo Понять что может храниться внутри "$model->onec['ЗначенияСвойств']['ЗначенияСвойства']" и переписать
*/ */
public static function createProperties1c($properties): void public static function createProperties1c($properties): void
{ {
// Инициализация // Инициализация
$models = static::searchOnecByAccountId(Yii::$app->user->id, true); $models = self::searchByAccount(Yii::$app->user->id, true);
$properties = self::xml2array($properties->xml); $properties = self::xml2array($properties->xml);
$fp = fopen('1.txt', 'a');
fwrite($fp, print_r(count($models), true) . PHP_EOL);
fclose($fp);
// for ($i = 0; $i <= count($models); $i++) // for ($i = 0; $i <= count($models); $i++)
foreach ($models as $model) { foreach ($models as $model) {
// Перебор записей // Перебор записей
$fp = fopen('2.txt', 'a');
fwrite($fp, $model->ocid . PHP_EOL);
fclose($fp);
// Инициализация // Инициализация
$changes = false; $changes = false;
$transit = $model->onec; $transit = $model->onec;
foreach ($model->onec['ЗначенияСвойств'] as $attribute_name => $attribute_value) { foreach ($model->onec['ЗначенияСвойств'] as $attribute_name => $attribute_value) {
// Перебор аттрибутов // Перебор аттрибутовfw
foreach ($properties as $property) { foreach ($properties as $property) {
// Перебор свойств // Перебор свойств
@ -149,6 +152,19 @@ class Supply extends Product implements ProductInterface
// Настройка ($transit нужен из-за ограничений __set()) // Настройка ($transit нужен из-за ограничений __set())
$model->onec = $transit; $model->onec = $transit;
foreach ($model->onec['ЗначенияСвойств'] as $property) {
// Перебор всех свойств
if (is_array($property)) {
if ($property['Ид'] === 'd99622fe-4526-11eb-b7f3-f3e52d0a06a9') {
// Если идентификатор свойства совпадает с указанным в настройках свойства хранящего OEM номера
// Настройка
$model->catn = $property['Значение'];
}
}
}
// Запись // Запись
$model->save(); $model->save();
} }
@ -165,14 +181,61 @@ class Supply extends Product implements ProductInterface
/** /**
* Запись изображений из 1С * Запись изображений из 1С
*
* @todo Добавить параметры в админ-панель
* Запретить доступ к изображениям
*/ */
public function addImage1c($path, $caption): mixed public function addImage1c($path, $caption): bool
{ {
return true; // Инициализация
$i = 0;
if (!file_exists(YII_PATH_PUBLIC . $catalog = '/img/supplies/' . $this->_key)) {
// Директория для изображений продукта не найдена
if (!mkdir(YII_PATH_PUBLIC . $catalog, 0775, true)) {
// не удалось записать директорию
return false;
};
}
foreach ($this->imgs ?? [] as $image) {
// Перебор имеющихся изображений
if ($path === $image['sorc']) {
// Изображение уже записано на сервер
return true;
}
}
// Инициализация
$urn = basename($path);
// Запись
copy($path, $path_local = YII_PATH_PUBLIC . $catalog . '/' . $urn);
// Запись свойства
$this->imgs = array_merge(
$this->imgs ?? [],
[
[
'desc' => $caption,
'path' => $path_local,
'sorc' => $path
]
]
);
// Отправка в базу данных
return $this->update();
} }
/** /**
* Запись ребра (предложения от поставок к продуктам) из 1С * Запись ребра (предложения от поставок к продуктам) из 1С
*
* @todo Разобраться зачем нужно возвращать SupplyEdgeProduct
*/ */
public function getOffer1c($offer): SupplyEdgeProduct public function getOffer1c($offer): SupplyEdgeProduct
{ {
@ -181,32 +244,64 @@ class Supply extends Product implements ProductInterface
// Разработчику библеотеки надо дать по жопе // Разработчику библеотеки надо дать по жопе
return new SupplyEdgeProduct; return new SupplyEdgeProduct;
} else if (!$product = Product::searchByCatn($this->catn)) { }
// Продукт не найден
if (!$this->initProduct()) { // Инициализация п̸̨͇͑͋͠р̷̬̂́̀̊о̸̜̯̹̅͒͘͝д̴̨̨̨̟̈́̆у̴̨̭̮̠́͋̈́к̴̭͊̋̎т̵̛̣͈̔̐͆а̵̨͖͑
// Не удалось инициализировать продукт $product = Product::initEmpty($this->catn);
// Разработчику библеотеки надо дать по жопе if (!is_array($product)) {
return new SupplyEdgeProduct; // Создался только один товар и вернулся в виде модели
$product = [$product];
}
if (is_array($this->oemn)) {
// Значение OEM было инициализировано
foreach ($this->oemn as $oem) {
// Перебор артикулов из массива ОЕМ-номеров
// Инициализация и запись
$product[] = Product::initEmpty($oem);
} }
} }
$product = Product::searchByCatn($this->catn);
// Запись ребра: ПОСТАВКА -> ПРОДУКТ
return (new SupplyEdgeProduct)->write( // $fp = fopen('555.txt', 'a');
$this->readId(), // fwrite($fp, print_r($property, true) . PHP_EOL);
$product->readId(), // fclose($fp);
'sell',
[
'onec' => self::xml2array($offer->xml) foreach ($product as $product) {
] // Перебор всех инициализированных продуктов
);
if (SupplyEdgeProduct::searchByVertex($this->readId(), $product->readId())) {
// Ребро уже существует
continue;
}
// Запись ребра: ПОСТАВКА -> ПРОДУКТ
$return = (new SupplyEdgeProduct)->write(
$this->readId(),
$product->readId(),
'sell',
[
'onec' => self::xml2array($offer->xml)
]
);
}
// Возвращает последнее сохранённое ребро
// Надо будет с этим разобраться
return $return ?? new SupplyEdgeProduct();
} }
/** /**
* Запись продукта из 1С * Запись продукта из 1С
*
* @todo Понять что может храниться внутри "$model->onec['ЗначенияСвойств']['ЗначенияСвойства']" и переписать
*/ */
public static function createModel1c($product): ?self public static function createModel1c($product): ?self
{ {
@ -216,38 +311,33 @@ class Supply extends Product implements ProductInterface
// Настройка // Настройка
$model->ocid = $id ?? null; $model->ocid = $id ?? null;
$model->catn = (string) $product->Артикул; $model->catn = (string) $product->Артикул;
$model->oemn = null;
$model->onec = self::xml2array($product->xml); $model->onec = self::xml2array($product->xml);
// Запись if (isset($model->onec['ЗначенияСвойств'])) {
return $model->save() ? $model : null; // Свойства инициализированы
}
/** foreach ($model->onec['ЗначенияСвойств'] as $property) {
* Инициализация продукта // Перебор всех свойств
*/
protected function initProduct(): ?Product
{
// Надо не забыть сделать выборку полей и ручное подключение
if (empty($this->catn)) { if (is_array($property)) {
// Не передан каталожный номер if ($property['Ид'] === 'd99622fe-4526-11eb-b7f3-f3e52d0a06a9') {
// Если идентификатор свойства совпадает с указанным в настройках свойства хранящего OEM номера
return false; // Настройка
} else if (Product::searchByCatn($this->catn)) { $model->oemn = array_merge(self::convertOemn2Catn($property['Значение']), self::convertOemn2Catn((string) $product->Артикул));
// Продукт уже был инициализирован }
}
return true; }
} }
// Инициализация
$product = new Product();
// Настройки
$product->catn = $this->catn;
// Запись // Запись
return $product->save() ? $product : null; if ($model->save()) {
// Поставка успешно сохранена
return $model;
}
return null;
} }
/** /**
@ -294,8 +384,18 @@ class Supply extends Product implements ProductInterface
return 'ocid'; return 'ocid';
} }
public static function searchOnecByAccountId(string $id, bool $full = false): array /**
* Поиск через связь с аккаунтом
*
* @param string $id Идентификатор пользователя
* @param bool $full Возврат всех значений (иначе только свойства)
*/
public static function searchByAccount(string $id = null, bool $full = false): array
{ {
if (!isset($id)) {
$id = yii::$app->user->id ?? throw new exception('Не найден идентификатор');
}
$subquery = static::find() $subquery = static::find()
->for(['account', 'account_edge_supply']) ->for(['account', 'account_edge_supply'])
->traversal('supply')->in('account_edge_supply') ->traversal('supply')->in('account_edge_supply')
@ -304,7 +404,7 @@ class Supply extends Product implements ProductInterface
->createCommand(); ->createCommand();
$query = static::find() $query = static::find()
->addParams($subquery->getBindVars()) ->params($subquery->getBindVars())
->let('account_edge_supply', '(' . (string) $subquery . ')') ->let('account_edge_supply', '(' . (string) $subquery . ')')
->where('supply._id == account_edge_supply[0]._to') ->where('supply._id == account_edge_supply[0]._to')
->andWhere('supply.onec["ЗначенияСвойств"] != null'); ->andWhere('supply.onec["ЗначенияСвойств"] != null');
@ -317,6 +417,8 @@ class Supply extends Product implements ProductInterface
$query = $query->select('supply.onec["ЗначенияСвойств"]')->createCommand()->execute()->getAll(); $query = $query->select('supply.onec["ЗначенияСвойств"]')->createCommand()->execute()->getAll();
foreach ($query as &$attribute) { foreach ($query as &$attribute) {
// Приведение всех свойств в массив и очистка от лишних данных
$attribute = $attribute->getAll(); $attribute = $attribute->getAll();
} }

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace app\models\traits;
use yii;
use exception;
trait SearchByAccount
{
/**
* Поиск через связи рёбрами с аккаунтом
*
* @param string $id Идентификатор пользователя
* @param int $limit Количество
* @param int $offset Сдвиг
* @param string $sort Сортировка
*/
public static function searchByAccount(
string $id = null,
int $limit = 10,
int $offset = 0,
array $sort = ['ASC'],
string|array $where = [],
string|array $post_where = [],
string $direction = 'ANY',
array $let = [],
array $params = []
): ?array {
if (!isset($id)) {
$id = yii::$app->user->id ?? throw new exception('Не найден идентификатор');
}
$subquery = static::find()
->for(['account', 'account_edge_' . self::collectionName()])
->traversal(self::collectionName(), $direction)
->in('account_edge_' . self::collectionName())
->where(['account._id' => $id]);
if (!empty($where)) {
// Переданы дополнительные условия фильтрации
$subquery->where($where);
}
$subquery = $subquery->select('account_edge_' . self::collectionName())
->createCommand();
$query = static::find()
->params($params, $subquery->getBindVars())
->let('account_edge_' . self::collectionName(), '(' . (string) $subquery . ')')
->limit($limit)
->offset($offset)
->orderBy($sort);
if (!empty($let)) {
// Переданы дополнительные условия фильтрации
$query->let(...$let);
}
if (isset($post_where) && $post_where) {
// Если переданы дополнительные условия фильтрации
$query->where($post_where);
} else {
$query->where(self::collectionName() . '._id == account_edge_' . self::collectionName() . '[0]._to');
}
return $query->select(self::collectionName())->all();
}
}

View File

@ -1,5 +1,7 @@
<?php <?php
declare(strict_types=1);
namespace app\models\traits; namespace app\models\traits;
trait Xml2Array { trait Xml2Array {

View File

@ -0,0 +1,18 @@
<?
declare(strict_types=1);
use yii;
?>
<a class="text-dark my-auto mr-2" href="/cart"><i class="fas fa-shopping-cart mx-2"></i></a>
<a class="text-dark my-auto mr-2" href="/orders"><i class="fas fa-list mx-2"></i></a>
<div class="btn-group my-auto">
<a class="btn m-0 px-0 text-dark button_clean" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();">Личный кабинет</a>
<button id="profile_button" class="btn pr-0 dropdown-toggle dropdown-toggle-split button_clean" type="button" data-toggle="dropdown" onmouseover="$('#profile_button').dropdown('show')"></button>
<div class="dropdown-menu dropdown-menu-long dropdown-menu-right p-3" aria-labelledby="profile_button" onmouseout="$('#profile_button').dropdown('show')">
<h5 class="mb-3 text-center">Аутентификация</h5>
<?= yii::$app->controller->renderPartial('/account/index') ?>
<!-- <a class="dropdown-item-text text-center px-0 py-2" href="#"><small>Восстановление пароля</small></a> -->
</div>
</div>

View File

@ -0,0 +1,28 @@
<?
declare(strict_types=1);
use yii;
if (!yii::$app->user->isGuest) {
$popup = yii::$app->controller->renderPartial('/notification/panel');
echo <<<HTML
<a id="notification_button" class="text-dark d-flex h-100 mr-2" title="Уведомления" href="/notification" role="button" data-toggle="dropdown" data-offset="-100%p + 100%" onclick="return notification_stream();">
<i class="fas fa-bell my-auto mx-2"></i>
</a>
<div id="notification_button_panel" class="dropdown-menu py-1" aria-labelledby="notification_button">
$popup
</div>
HTML;
}
?>
<a class="text-dark my-auto mr-2" href="/cart"><i class="fas fa-shopping-cart mx-2"></i></a>
<a class="text-dark my-auto mr-2" href="/orders"><i class="fas fa-list mx-2"></i></a>
<div class="btn-group my-auto">
<a class="btn m-0 px-0 text-dark button_clean" title="Личный кабинет" href="/profile" role="button" onclick="return page_profile();">Личный кабинет</a>
<button id="profile_button" class="btn pr-0 dropdown-toggle dropdown-toggle-split button_clean" type="button" data-toggle="dropdown" onmouseover="$('#profile_button').dropdown('show')"></button>
<div id="profile_button_panel" class="dropdown-menu dropdown-menu-right py-1" aria-labelledby="profile_button" onmouseout="$('#profile_button').dropdown('show')">
<a class="dropdown-item button_white text-dark" onclick="deauthentication()">Выход (<?= yii::$app->user->identity->mail ?>)</a>
</div>
</div>

View File

@ -12,7 +12,7 @@ $this->title = 'SkillParts';
<div class="container h-100 d-flex flex-column justify-content-center"> <div class="container h-100 d-flex flex-column justify-content-center">
<p class="mb-4 ml-0 gilroy">Проблема с подбором запчастей?</p> <p class="mb-4 ml-0 gilroy">Проблема с подбором запчастей?</p>
<p class="ml-0 d-flex"> <p class="ml-0 d-flex">
<span class="p-2 px-3 button_call_icon"><i class="fas fa-phone-alt text-white"></i></span> <span class="p-2 px-3 button_call_icon d-flex"><i class="fas fa-phone-alt my-auto text-white"></i></span>
<a class="btn text-white button_clean button_blue button_call" href="/call" role="button">Связаться с менеджером</a> <a class="btn text-white button_clean button_blue button_call" href="/call" role="button">Связаться с менеджером</a>
</p> </p>
</div> </div>
@ -22,21 +22,21 @@ $this->title = 'SkillParts';
</div> </div>
<div class="h-100 d-flex ticker"> <div class="h-100 d-flex ticker">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/cummins.png" alt="Cummins"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/cummins.png" alt="Cummins">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/iveco.png" alt="Iveco"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/iveco.png" alt="Iveco">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/komatsu.png" alt="Komatsu"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/komatsu.png" alt="Komatsu">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/case.png" alt="Case"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/case.png" alt="Case">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/isuzu.png" alt="Isuzu"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/isuzu.png" alt="Isuzu">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/new_holland.svg" alt="New Holland"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/new_holland.png" alt="New Holland">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/perkins.png" alt="Perkins"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/perkins.png" alt="Perkins">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/john_deere.png" alt="John Deere"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/john_deere.png" alt="John Deere">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/caterpillar.png" alt="Caterpillar"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/caterpillar.png" alt="Caterpillar">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/shantui.png" alt="Shantui"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/shantui.png" alt="Shantui">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/xcmg.png" alt="XCMG"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/xcmg.png" alt="XCMG">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/kobelco.png" alt="Kobelco"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/kobelco.png" alt="Kobelco">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/shehwa.png" alt="SHEHWA"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/shehwa.png" alt="SHEHWA">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/bomag.png" alt="BOMAG"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/bomag.png" alt="BOMAG">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h100px/compressed/hitachi.png" alt="Hitachi"> <img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/hitachi.png" alt="Hitachi">
</div> </div>
<div class="container mb-4"> <div class="container mb-4">

View File

@ -1,9 +1,9 @@
<?php <?php
/* @var $this \yii\web\View */ declare(strict_types=1);
/* @var $content string */
use yii\helpers\Html; use yii\helpers\Html;
use app\assets\AppAsset; use app\assets\AppAsset;
AppAsset::register($this); AppAsset::register($this);
@ -27,19 +27,21 @@ AppAsset::register($this);
<body> <body>
<?php $this->beginBody() ?> <?php $this->beginBody() ?>
<header class="container pt-2 mt-1 mb-4"> <div id="notifications_popup_wrap" class="col-3 m-4"></div>
<header class="container pt-2 mt-1 mb-2 mb-sm-4">
<div class="row h-100"> <div class="row h-100">
<a class="col-3 h-100 py-2" title="SkillParts" href="/" role="button" onclick="return page_main();"> <a id="logo" class="col-3 col-md-4 h-100 py-2" title="SkillParts" href="/" role="button" onclick="return page_main();">
<img class="h-100" src="/img/logos/skillparts.svg" alt="SkillParts"> <img class="h-100" src="/img/logos/skillparts.svg" alt="SkillParts">
</a> </a>
<div class="col-6 px-0 mt-auto d-flex"> <div class="col px-0 mt-auto d-flex h-title">
<div class="ml-auto p-0 h-divider-title-left"></div> <div class="ml-auto p-0 h-divider-title-left"></div>
<div class="px-4 d-flex flex-column justify-content-center h-divider-title"> <div class="px-4 d-flex flex-column justify-content-center h-divider-title">
<h6 class="text-center text-white my-0"><b>Запчасти для спецтехники</b></h6> <h6 class="text-center text-white my-0"><b>Запчасти для спецтехники</b></h6>
</div> </div>
<div class="mr-auto p-0 h-divider-title-right"></div> <div class="mr-auto p-0 h-divider-title-right"></div>
</div> </div>
<menu class="col-3 mb-0 d-flex justify-content-end"></menu> <menu class="col-auto col-lg-4 mb-0 d-flex justify-content-end"></menu>
</div> </div>
<div class="h-divider"></div> <div class="h-divider"></div>
</header> </header>
@ -66,11 +68,21 @@ AppAsset::register($this);
<input id="catalog_search_panel_button_3" class="btn btn-sm5 text-white button_clean" type="radio" name="catalog_search_panel_buttons" value="catalog_search_panel_button_3"> <input id="catalog_search_panel_button_3" class="btn btn-sm5 text-white button_clean" type="radio" name="catalog_search_panel_buttons" value="catalog_search_panel_button_3">
<label class="mb-0 px-3 px-md-4 py-1" for="catalog_search_panel_button_3">Третья кнопка</label> --> <label class="mb-0 px-3 px-md-4 py-1" for="catalog_search_panel_button_3">Третья кнопка</label> -->
<form class="d-flex catalog_search" onsubmit="return false;"> <form class="d-flex catalog_search" onsubmit="return false;">
<div class="position-relative col-8 col-lg-10 px-0"> <div class="position-relative col-sm-8 col-lg-10 px-0">
<input id="search_line" type="text" class="form-control col-12 catalog_search_line button_clean" placeholder="Введите номер запчасти, например: 45223503481" onclick="$('#search_line').dropdown('toggle')" onmouseenter="$('#search_line').dropdown('show')" oninput="$('#search_line').dropdown('hide'); product_search(this);" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <input id="search_line" type="text" class="form-control col-12 catalog_search_line button_clean" placeholder="Введите номер запчасти, например: 45223503481" oninput="$('#search_line').dropdown('hide'); product_search(this);" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" autocomplete="off">
<div id="search_line_window" class="dropdown-menu w-100" aria-labelledby="search_line" onmouseout="$('#search_line').dropdown('show')"> <?
<a class="dropdown-item button_white text-dark inactive">Здесь будет история поиска</a> if (!yii::$app->user->isGuest && $search_panel = $search_panel ?? yii::$app->controller->renderPartial('/search/panel', ['history' => true])) {
</div> echo <<<HTML
<div id="search_line_window" class="dropdown-menu w-100" aria-labelledby="search_line">
$search_panel
</div>
HTML;
} else {
echo <<<HTML
<div id="search_line_window" class="dropdown-menu w-100 d-none" aria-labelledby="search_line"></div>
HTML;
}
?>
</div> </div>
<button type="submit" class="col btn button_clean catalog_search_button" onclick="product_search(this.parentElement.getElementsByTagName('input')[0], 1)">ПОИСК</button> <button type="submit" class="col btn button_clean catalog_search_button" onclick="product_search(this.parentElement.getElementsByTagName('input')[0], 1)">ПОИСК</button>
</form> </form>

View File

@ -0,0 +1,30 @@
<?
if (empty($notifications)) {
echo <<<HTML
<p class="px-2 py-3 text-center">Уведомлений нет</p>
HTML;
return;
}
foreach($notifications as $notification) {
// Перебор уведомлений
// Инициализация
$notification = $notification->getAttributes();
if ($notification['type'] === 'notice') {
// Уведомление
echo $notification['html'];
} else if ($notification['type'] === 'warning') {
// Предупреждение
echo $notification['html'];
} else {
// Неизвестно
echo $notification['html'] ?? '<p>ОШИБКА</p>';
}
}
?>

View File

@ -0,0 +1,10 @@
<?
$id = 'popup/' . $notification->readId();
$html = $notification->html;
echo <<<HTML
<div id="$id" class="mt-3 p-0 notification rounded" onmouseleave="return notification_popup_delete(this);">
$html
</div>
HTML;
?>

View File

@ -1,5 +1,10 @@
<?php <?php
use yii\bootstrap\ActiveForm;
use yii\helpers\Html;
use app\models\Product;
?> ?>
<link href="/css/pages/product.css" rel="stylesheet"> <link href="/css/pages/product.css" rel="stylesheet">
@ -7,11 +12,117 @@
<div class="container h-100"> <div class="container h-100">
<div class="row h-100 py-3"> <div class="row h-100 py-3">
<article class="col-12"> <article class="col-12">
<div class="h-100 p-3 rounded"> <div class="h-100 p-3 d-flex flex-column rounded">
<h4 class="ml-4"><?php echo $model['name'] ?></h4> <div id="product_slider" class="row px-3 profile_panel">
<div class="dropdown-divider"></div> <div class="col-1 product_slider_preview p-0 pr-3 mb-3">
<p class="ml-4">Дата создания: <time><?php echo date('d.m.Y H:i', $model['date']) ?></time></p> <?
<pre class="ml-4"><?php var_dump($model) ?></pre> foreach ($model['imgs'] ?? [null] as $key => $image) {
// Перебор изображений
// Инициализация
$name = $image['name'] ?? 'Без названия';
$h150 = $image['h150'] ?? '/img/covers/h150/product.png';
// Генерация предпросмотра изображения
echo <<<HTML
<label class="p-0 mb-2" for="product_slider_image_$key">
<img class="img-fluid rounded" src="$h150"/>
</label>
HTML;
}
?>
</div>
<div class="product_slider_image">
<?
// Инициализация
$imgs = $model['imgs'] ?? [null];
$checked = '';
foreach ($imgs as $key => $image) {
// Перебор изображений
// Инициализация
$name = $image['name'] ?? 'Без названия';
$orig = $image['orig'] ?? '/img/covers/product.png';
$covr = $image['covr'] ?? false;
if ($covr || count($imgs) < 2) {
// Если это изображение является обложкой
// Реинициализация
$checked = 'checked';
}
// Генерация изображения
echo <<<HTML
<input type="radio" id="product_slider_image_$key" name="slider" $checked/>
<div class="col p-0">
<img class="img-fluid rounded" src="$orig"/>
</div>
HTML;
// Деинициализация
$checked = '';
}
?>
</div>
<div class="col ml-4 d-flex flex-column">
<div class="row mb-1">
<h3 class="my-auto">Название товара</h3>
<h5 class="ml-auto my-auto"><?= $model['catn'] ?></h5>
</div>
<div class="dropdown-divider px-0 mb-3"></div>
<div class="row mb-3 h-100 product_panel d-flex flex-column">
<p class="mt-0">
ОЕМ-номера можно сюда добавить с возможностью перехода
<?
// foreach ($model['catn'] ?? [] as $catn) {
// echo <<<HTML
// $catn
// HTML;
// }
?>
</p>
</div>
<?
$form = ActiveForm::begin([
'id' => 'form_product_cart',
'action' => false,
'fieldConfig' => [
'template' => '{input}',
],
'options' => [
'onsubmit' => 'return false;',
'class' => 'row mt-auto'
]
]);
// Просто для теста
$model = new Product();
?>
<div class="col-6 px-0 mr-4 btn-group">
<?= Html::submitButton('В корзину', ['name' => 'cartWrite', 'onclick' => 'product_cart_write(this.parentElement);', 'class' => 'col-10 btn button_blue button_clean py-2 px-5']) ?>
<?= $form->field($model, 'amount', ['options' => ['class' => 'col h-100 m-0 form-group']])->textInput(['value' => '1', 'class' => 'form-control h-100 rounded-0 text-center button_clean']); ?>
</div>
<div>
<p class="mt-0">
<strong>Хабаровск: </strong>
в наличии
</p>
<p class="mb-0">
Доставим: завтра
</p>
</div>
<? ActiveForm::end(); ?>
</div>
</div>
<div class="row mt-auto mx-0">
<p class="ml-0">Время для повышения релевантности в поисковиках</p>
<time class="ml-auto"><?php echo date('d.m.Y', $model['date']) ?></time>
</div>
</div> </div>
</article> </article>
</div> </div>

View File

@ -1,8 +1,12 @@
<?php <?php
use yii\bootstrap\ActiveForm; declare(strict_types=1);
use app\models\Supply;
use yii;
use yii\bootstrap\ActiveForm;
use yii\helpers\Html;
use app\models\Notification;
?> ?>
<link href="/css/pages/profile.css" rel="stylesheet"> <link href="/css/pages/profile.css" rel="stylesheet">
@ -10,46 +14,100 @@ use app\models\Supply;
<div id="page_profile" class="container h-100"> <div id="page_profile" class="container h-100">
<div class="row h-100 py-3"> <div class="row h-100 py-3">
<nav class="col-3"> <nav class="col-3">
<?= $sidebar ?? Yii::$app->controller->renderPartial('/profile/sidebar') ?> <?= $sidebar ?? yii::$app->controller->renderPartial('/profile/sidebar') ?>
</nav> </nav>
<article class="col-9"> <article class="col-9">
<div class="h-100 p-4 rounded"> <div class="p-4 rounded">
<h4 class="ml-4 mb-4">Настройки аккаунта</h4> <h4 class="ml-4 mb-4"><i class="fas fa-sliders-h my-auto mr-2"></i>Настройки</h4>
<p>Пока не сделана нормальная панель управления я просто буду добавлять все настройки сюда</p> <div id="profile_panel_settings" class="profile_panel">
<?php <div class="profile_panel_menu mb-3">
$form = ActiveForm::begin([ <label class="btn button_white mb-0 mr-2" for="profile_panel_settings_account">Аккаунт</label>
'id' => 'form_profile_options', <label class="btn button_white mb-0 mr-2" for="profile_panel_settings_company">Компания</label>
'action' => false, <label class="btn button_white mb-0" for="profile_panel_settings_import">Импорт</label>
'fieldConfig' => [ </div>
'template' => '{label}{input}', <div class="profile_panel_content d-flex">
'options' => ['class' => ''] <input type="radio" id="profile_panel_settings_account" name="main_panel" />
], <div class="col">
'options' => [ 1
'class' => 'mb-3', </div>
'onsubmit' => 'return false;'
]
]);
$model = $model ?? Yii::$app->user->identity; <input type="radio" id="profile_panel_settings_company" name="main_panel" />
var_dump($attributes = Supply::searchOnecByAccountId(Yii::$app->user->id)); <div class="col">
2
</div>
// $list = []; <input type="radio" id="profile_panel_settings_import" name="main_panel" checked />
<div class="col">
<h5>Параметры 1C</h5>
<div class="dropdown-divider mb-3"></div>
<?
$form = ActiveForm::begin([
'id' => 'form_profile_settings',
'action' => false,
'fieldConfig' => [
'template' => '{label}{input}',
],
'options' => [
'onsubmit' => 'return false;'
]
]);
// // /**
// foreach ($attributes as $attribute) { * @todo Перенести в модель
// $id = $attribute['ЗначенияСвойства']['Ид']; */
// if (in_array($id, $list, true)) { // Инициализация
// continue; isset($model) or $model = yii::$app->user->identity;
// } isset($attributes) or $attributes = Supply::searchByAccount(yii::$app->user->id);
$list = [];
// $list[] = $id; // Перебор свойств поставок
// } foreach ($attributes as $attribute) {
// Инициализация
$id = $attribute['ЗначенияСвойства']['Ид'];
?> if (in_array($id, $list, true)) {
<?= "a" //$form->field($model, 'opts[import_sections_oem]', ['options' => ['class' => "mb-3"]])->dropDownList($list ?? ['Нет данных']); ?> // Если встретился дубликат (вызывается очень часто)
continue;
}
<?php ActiveForm::end(); ?> // Генерация
$list[$id] = $attribute['ЗначенияСвойства']['Наименование'];
}
// Инициализация текущего значения параметра в начале массива
if (isset($model->opts['import_sections_oem'])) {
// Параметр 'import_sections_oem' найден в настройках аккаунта
if (isset($list[$model->opts['import_sections_oem']])) {
// Найдено совпадение сохранённого параметра с полученным списком из поставок
// Буфер для сохранения параметра
$buffer = $list[$model->opts['import_sections_oem']];
// Удаление параметра
unset($list[$model->opts['import_sections_oem']]);
// Сохранение параметра в начале массива
$list = array_merge([$model->opts['import_sections_oem'] => $buffer], $list);
} else {
// Совпадение не найдено
// Сохранение параметра из данных аккаунта в начале массива
$list = array_merge([$model->opts['import_sections_oem'] => $model->opts['import_sections_oem']], $list);
}
}
?>
<?= $form->field($model, 'opts[import_sections_oem]', ['options' => ['class' => "mb-1"]])->dropDownList($list ?? ['Нет данных'], ['onChange' => 'page_profile_settings(this.parentElement.parentElement)'])->label('OEM-номера'); ?>
<small class="d-block mb-1">Выберите поле в котором хранятся <b>ОЕМ-номера</b> и повторите импорт</small>
<small class="d-block">Значения взяты из импортированных товаров</small>
<!-- <small class="d-block mb-3">Значения взяты из импортированных товаров</small> -->
<? ActiveForm::end(); ?>
</div>
</div>
</div>
</div> </div>
</article> </article>
</div> </div>

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
?>
<link href="/css/pages/profile.css" rel="stylesheet">
<div id="page_profile" class="container h-100">
<div class="row h-100 py-3">
<nav class="col-3">
<?= $sidebar ?? Yii::$app->controller->renderPartial('/profile/sidebar') ?>
</nav>
<article class="col-9">
<div class="p-4 rounded">
<h4 class="ml-3 mb-4"><i class="fas fa-eye my-auto mr-2"></i>Мониторинг</h4>
<div id="profile_panel_monitoring" class="profile_panel">
<div class="profile_panel_menu mb-3">
<label class="btn button_white mb-0 mr-2" for="profile_panel_monitoring_input_orders_history">Заказы</label>
<label class="btn button_white mb-0 mr-2" for="profile_panel_monitoring_input_search_history">Поисковые запросы</label>
<label class="btn button_white mb-0" for="profile_panel_monitoring_input_log">Журнал действий</label>
</div>
<div class="profile_panel_content">
<input type="radio" id="profile_panel_monitoring_input_orders_history" name="main_panel" />
<div class="col p-3">
История заказов в разработке
</div>
<input type="radio" id="profile_panel_monitoring_input_search_history" name="main_panel" checked />
<div class="col">
<div class="row header_blue py-2 text-white">
<div class="col-sm-4 col-md-3">IPv4</div>
<div class="col-7 col-sm-3 col-md-4 col-lg-6 pr-0 px-sm-0 px-lg-3">Поисковый запрос</div>
<div class="col">Время</div>
</div>
<?
foreach ($search_history as $row) {
// Инициализация
$date = date('H:i d.m.Y', $row->date);
echo <<<HTML
<div class="row py-1">
<div class="col-sm-4 col-md-3">$row->ipv4</div>
<div class="col-7 col-sm-3 col-md-4 col-lg-6 pr-0 px-sm-0 px-lg-3">$row->text</div>
<div class="col">$date</div>
</div>
HTML;
}
?>
<div class="mt-3">
<?
$page = ($page_search_history ?? 0) + 1;
echo <<<HTML
<div class="row">
<button class="btn button_clean button_blue" onclick="return page_profile_monitoring(-1);">Назад</button>
<p>$page</p>
<button class="btn button_clean button_blue" onclick="return page_profile_monitoring(1);">Вперёд</button>
<div class="col-8"></div>
</div>
HTML;
?>
</div>
</div>
<input type="radio" id="profile_panel_monitoring_input_log" name="main_panel" />
<div class="col p-3">
Журнал в разработке
</div>
</div>
</div>
</div>
</article>
</div>
</div>
<script src="/js/profile.js" defer></script>

View File

@ -2,27 +2,105 @@
declare(strict_types=1); declare(strict_types=1);
use Yii; use yii;
use app\models\Product; use app\models\Product;
use app\models\Supply; use app\models\Supply;
?> use app\models\AccountEdgeSupply;
use app\models\SupplyEdgeProduct;
<div class="p-3 rounded"> ?>
<div class="col py-3 rounded">
<div class="row px-3"> <div class="row px-3">
<p class="ml-0">Почта: </p> <p class="ml-0">Почта: </p>
<p class="mr-0"><?= Yii::$app->user->identity->mail ?></p> <p class="mr-0"><?= yii::$app->user->identity->mail ?></p>
</div> </div>
<div class="dropdown-divider my-3"></div> <div class="dropdown-divider my-3"></div>
<dl class="m-0"> <dl class="m-0">
<?
if (yii::$app->user->identity->trst) {
// Пользователь является доверенным
// Инициализация
$targetUrl = '/profile/trusted';
if ('/' . yii::$app->request->pathInfo === $targetUrl) {
// Запрошена эта страница
echo <<<HTML
<dt>
<a class="row text-dark button_white button_white_hover px-3 py-3 py-lg-2 font-weight-normal" href="$targetUrl"><i class="fas fa-sliders-h my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Панель управления</span></a>
</dt>
HTML;
} else {
echo <<<HTML
<dt>
<a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" href="$targetUrl"><i class="fas fa-sliders-h my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Панель управления</span></a>
</dt>
HTML;
}
}
?>
<dt> <dt>
<a class="row text-dark mb-3 px-3 font-weight-normal" href="/profile">Настройки аккаунта</a> <?
// Инициализация
$targetUrl = '/profile/supplies';
if ('/' . yii::$app->request->pathInfo === $targetUrl) {
// Запрошена эта страница
echo <<<HTML
<a class="row text-dark button_white button_white_hover px-3 py-3 py-lg-2 font-weight-normal" href="$targetUrl"><i class="fas fa-truck my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Поставки</span></a>
HTML;
} else {
echo <<<HTML
<a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" href="$targetUrl"><i class="fas fa-truck my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Поставки</span></a>
HTML;
}
?>
</dt> </dt>
<dt> <dt>
<a class="row text-dark px-3 font-weight-normal" href="/profile/supplies">Управление поставками</a> <?
// Инициализация
$targetUrl = '/profile/monitoring';
if ('/' . yii::$app->request->pathInfo === $targetUrl) {
// Запрошена эта страница
echo <<<HTML
<a class="row text-dark button_white button_white_hover px-3 py-3 py-lg-2 font-weight-normal" href="$targetUrl"><i class="fas fa-eye my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Мониторинг</span></a>
HTML;
} else {
echo <<<HTML
<a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" href="$targetUrl"><i class="fas fa-eye my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Мониторинг</span></a>
HTML;
}
?>
</dt>
<dt>
<?
// Инициализация
$targetUrl = '/profile';
if ('/' . yii::$app->request->pathInfo === $targetUrl) {
// Запрошена эта страница
echo <<<HTML
<a class="row text-dark button_white button_white_hover px-3 py-3 py-lg-2 font-weight-normal" href="$targetUrl"><i class="fas fa-sliders-h my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Настройки</span></a>
HTML;
} else {
echo <<<HTML
<a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" href="$targetUrl"><i class="fas fa-sliders-h my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Настройки</span></a>
HTML;
}
?>
</dt> </dt>
</dl> </dl>
<div class="dropdown-divider my-3"></div> <div class="dropdown-divider my-3"></div>
<div class="row px-3">
<p class="ml-0">Загрузок с аккаунта:</p>
<p class="mr-0"><?= AccountEdgeSupply::readAmount() ?></p>
</div>
<div class="row px-3"> <div class="row px-3">
<p class="ml-0">Товары:</p> <p class="ml-0">Товары:</p>
<p class="mr-0"><?= Product::readAmount() ?></p> <p class="mr-0"><?= Product::readAmount() ?></p>
@ -31,4 +109,8 @@ use app\models\Supply;
<p class="ml-0">Поставки:</p> <p class="ml-0">Поставки:</p>
<p class="mr-0"><?= Supply::readAmount() ?></p> <p class="mr-0"><?= Supply::readAmount() ?></p>
</div> </div>
<div class="row px-3">
<p class="ml-0">Связано поставок:</p>
<p class="mr-0"><?= SupplyEdgeProduct::readAmount() ?></p>
</div>
</div> </div>

View File

@ -2,6 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
use yii;
use yii\bootstrap\ActiveForm; use yii\bootstrap\ActiveForm;
use app\controllers\ProfileController; use app\controllers\ProfileController;
@ -13,7 +14,7 @@ use app\models\Supply;
<div id="page_profile" class="container h-100"> <div id="page_profile" class="container h-100">
<div class="row h-100 py-3"> <div class="row h-100 py-3">
<nav class="col-3"> <nav class="col-3">
<?= $sidebar ?? Yii::$app->controller->renderPartial('/profile/sidebar') ?> <?= $sidebar ?? yii::$app->controller->renderPartial('/profile/sidebar') ?>
</nav> </nav>
<article class="col-9"> <article class="col-9">
<div class="h-100 p-4 rounded"> <div class="h-100 p-4 rounded">

View File

@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
use yii;
use yii\bootstrap\ActiveForm;
use yii\helpers\Html;
use app\models\Notification;
use app\models\Settings;
?>
<link href="/css/pages/profile.css" rel="stylesheet">
<div id="page_profile" class="container h-100">
<div class="row h-100 py-3">
<nav class="col-3">
<?= $sidebar ?? yii::$app->controller->renderPartial('/profile/sidebar') ?>
</nav>
<article class="col-9">
<div class="p-4 rounded">
<h4 class="ml-4 mb-4"><i class="fas fa-user-shield my-auto mr-2"></i>Панель управления</h4>
<div id="profile_panel_trusted" class="profile_panel">
<div class="profile_panel_menu mb-3">
<label class="btn button_white mb-0 mr-2" for="profile_panel_trusted_input_notifications">Уведомления</label>
<label class="btn button_white mb-0 mr-2" for="profile_panel_trusted_input_settings">Настройки</label>
</div>
<div class="profile_panel_content">
<input type="radio" id="profile_panel_trusted_input_notifications" name="main_panel" />
<div class="col">
<h5>Отправка уведомления</h5>
<div class="dropdown-divider mb-3"></div>
<?
$form = ActiveForm::begin([
'id' => 'form_profile_trusted_notifications',
'action' => false,
'fieldConfig' => [
'template' => '{label}{input}'
],
'options' => [
'onsubmit' => 'return false;'
]
]);
// Значения по умолчанию
isset($model_notifications) or $model_notifications = new Notification;
isset($model_notifications->trgt) or $model_notifications->trgt = null;
isset($model_notifications->text) or $model_notifications->text = '';
?>
<?= $form->errorSummary($model_notifications) ?>
<div class="row">
<?= $form->field($model_notifications, 'trgt', ['options' => ['class' => "mb-1 col-9"]])->input('text', ['placeholder' => yii::$app->user->identity->_key]); ?>
<?= $form->field($model_notifications, 'type', ['options' => ['class' => "col pl-0"]])->dropDownList($model_notifications->typs); ?>
</div>
<small class="d-block mb-1"><b>Множественная отправка:</b> @all или перечислить через запятую</small>
<small class="d-block mb-3"><b>Тестирование:</b> @test или оставить поле пустым</small>
<?= $form->field($model_notifications, 'text', ['options' => ['class' => "mb-3"]])->textarea(); ?>
<?= Html::submitButton('Отправить', ['name' => 'submitNotification', 'onclick' => 'page_profile_trusted_notification_create(this.parentElement);', 'class' => 'flex-grow-1 mr-2 btn button_white button_clean']) ?>
<?= Html::submitButton('Отправить как HTML', ['name' => 'submitNotification', 'onclick' => 'page_profile_trusted_notification_create(this.parentElement, 1);', 'class' => 'flex-grow-1 mr-2 btn button_white button_clean']) ?>
<? ActiveForm::end(); ?>
</div>
<input type="radio" id="profile_panel_trusted_input_settings" name="main_panel" checked />
<div class="col">
<?
$form = ActiveForm::begin([
'id' => 'form_profile_trusted_settings_search_period',
'action' => false,
'fieldConfig' => [
'template' => '{label}{input}',
],
'options' => [
'onsubmit' => 'return false;',
'class' => 'mb-4'
]
]);
?>
<?= $form->errorSummary($model_settings, ['header' => 'Получены ошибки:']) ?>
<?= $form->field($model_settings, 'search_period', ['options' => ['class' => "mb-1"]])->textInput(['value' => $model_settings['search_period'], 'onChange' => 'page_profile_trusted_settings(this.parentElement.parentElement)']); ?>
<small class="d-block mb-1">Время которое надо ждать для повторного поиска в секундах</small>
<? ActiveForm::end(); ?>
<?
$form = ActiveForm::begin([
'id' => 'form_profile_trusted_settings_search_connect_keep',
'action' => false,
'fieldConfig' => [
'template' => '{label}{input}',
],
'options' => [
'onsubmit' => 'return false;'
]
]);
$list = ['false' => 'Нет', 'true' => 'Да'];
if (isset($model_settings->search_connect_keep)) {
// Найден ранее записанный параметр
if (isset($list[$model_settings->search_connect_keep])) {
// Совпадение параметра с параметрами списка найдено
// Буфер для сохранения параметра
$buffer = $list[$model_settings->search_connect_keep];
// Удаление параметра
unset($list[$model_settings->search_connect_keep]);
// Сохранение параметра в начале массива
$list = array_merge([$model_settings->search_connect_keep => $buffer], $list);
} else {
// Совпадение параметра с параметрами списка не найдено
// Сохранение параметра из данных аккаунта в начале массива
$list = array_merge([$model_settings->search_connect_keep => $model_settings->search_connect_keep], $list);
}
}
?>
<?= $form->field($model_settings, 'search_connect_keep', ['options' => ['class' => "mb-1"]])->dropDownList($list, ['onChange' => 'page_profile_trusted_settings(this.parentElement.parentElement)']); ?>
<small class="d-block mb-1">Удерживать открытое соединение до истечения срока блокировки поиска?</small>
<small class="d-block mb-1">При малой задержке позволяет снизить время загрузки страницы, но при большой будет казаться, что сайт завис</small>
<? ActiveForm::end(); ?>
</div>
</div>
</div>
</div>
</article>
</div>
</div>
<script src="/js/profile.js" defer></script>
<script src="/js/profile_trusted.js" defer></script>

View File

@ -8,31 +8,70 @@
</div> </div>
</nav> </nav>
<article class="col-9"> <article class="col-9">
<div class="row p-3 rounded"> <div class="row mx-0 p-3 rounded">
<div class="d-flex flex-column mx-auto"> <?php
<?php if (isset($timer) && $timer > 0) {
if (isset($timer) && $timer > 0) { echo <<<HTML
echo <<<HTML <div class="d-flex flex-column mx-auto">
<p class="d-flex justify-content-center mb-3">Слишком частые запросы, повторите попытку через: $timer секунд</p> <p class="px-2 mb-1 text-center d-flex justify-content-center">Слишком частые запросы, повторите попытку через: $timer секунд</p>
<small class="mb-3 text-center d-flex justify-content-center">Подождите или нажмите на кнопку вручную</small>
<a class="btn text-white button_clean button_blue mx-auto" onclick="window.location.reload();">Повторить</a> <a class="btn text-white button_clean button_blue mx-auto" onclick="window.location.reload();">Повторить</a>
HTML; </div>
} else { <script type="text/javascript">
setTimeout('window.location.reload()', $timer + '000');
</script>
HTML;
} else {
if (isset($response) && is_array($response) && $response) {
if (isset($response) && $response) { $i = 0;
foreach ($response as $row) { $amount = count($response);
$id = $row['id'];
$catn = $row['catn'];
foreach ($response as $row) {
$catn = $row['catn'];
$covr = $row['covr'] ?? '/img/covers/h150/product.png';
$amnt = $row['amnt'] ?? 1;
// <button class="button_clean_full d-flex p-0">
// <i class="fas fa-plus-square search_card_cart_icon my-auto"></i>
// </button>
echo <<<HTML
<div class="col-3 p-2 d-flex flex-column search_card rounded">
<div class="col">
<div class="row mb-2">
<img class="w-100 img-fluid rounded" src="$covr"/>
</div>
<div class="row mb-2">
<a class="mx-auto text-dark" href="/product/$catn"><h5 class="m-0">$catn</h5></a>
</div>
<div class="row px-2 mb-2">
<small class="mt-auto">{$amnt}шт</small>
<p class="mr-0">1000р</p>
</div>
<div class="row">
<button class="btn button_grey button_clean w-100 text-white">В корзину</button>
</div>
</div>
</div>
HTML;
if (++$i % 4 === 0 && $amount - $i !== 0) {
echo <<<HTML echo <<<HTML
<a class="dropdown-item button_white text-dark" href="/product/$id">$catn</a> <div class="dropdown-divider my-3 mx-4 w-100 "></div>
HTML; HTML;
} }
} else {
echo '<p class="m-0 py-2 d-flex justify-content-center">Ничего не найдено</p>';
} }
} else {
echo <<<HTML
<div class="d-flex flex-column mx-auto">
<p class="m-0 py-2 d-flex justify-content-center">Ничего не найдено</p>
</div>
HTML;
} }
?> }
</div> ?>
</div> </div>
</article> </article>
</div> </div>

View File

@ -1,22 +1,38 @@
<?php <?php
use yii\helpers\Html; declare(strict_types=1);
use yii\bootstrap\ActiveForm;
use app\models\AccountForm;
// var_dump($response); use app\models\Search;
if (isset($history) && $history) {
// Отображение истории
if (!yii::$app->user->isGuest && $rows = Search::searchByAccount(sort: ['DESC'])) {
// История поиска существует
foreach ($rows as $row) {
// Инициализация
$date = date('H:i d.m.Y', $row->date);
echo <<<HTML
<a class="dropdown-item d-flex button_white text-dark" href="/search?type=product&q=$row->text">$row->text<span class="ml-auto">$date</span></a>
HTML;
}
}
} else if (isset($response) && $response) {
// Ответ получен
if (isset($response) && $response) {
foreach ($response as $row) { foreach ($response as $row) {
$id = $row['id']; // Перебор найденных данных
$catn = $row['catn']; $catn = $row['catn'];
echo <<<HTML echo <<<HTML
<a class="dropdown-item button_white text-dark" href="/product/$id">$catn</a> <a class="dropdown-item button_white text-dark" href="/product/$catn">$catn</a>
HTML; HTML;
} }
} else { } else {
echo '<p class="m-0 py-2 d-flex justify-content-center">Ничего не найдено</p>'; echo <<<HTML
<p class="m-0 py-2 d-flex justify-content-center">Ничего не найдено</p>
HTML;
} }
?>

View File

@ -1,7 +1,7 @@
/*! /*!
* Bootstrap Grid v4.5.3 (https://getbootstrap.com/) * Bootstrap Grid v4.6.0 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors * Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc. * Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
html { html {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
/*! /*!
* Bootstrap Reboot v4.5.3 (https://getbootstrap.com/) * Bootstrap Reboot v4.6.0 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors * Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc. * Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/ */
@ -24,7 +24,7 @@ article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
line-height: 1.5; line-height: 1.5;
@ -198,9 +198,8 @@ button {
border-radius: 0; border-radius: 0;
} }
button:focus { button:focus:not(:focus-visible) {
outline: 1px dotted; outline: 0;
outline: 5px auto -webkit-focus-ring-color;
} }
input, input,

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,8 @@
/*! /*!
* Bootstrap Reboot v4.5.3 (https://getbootstrap.com/) * Bootstrap Reboot v4.6.0 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors * Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc. * Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */ /*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
/*! /*!
* Bootstrap v4.5.3 (https://getbootstrap.com/) * Bootstrap v4.6.0 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors * Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc. * Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/ */
:root { :root {
@ -31,7 +31,7 @@
--breakpoint-md: 768px; --breakpoint-md: 768px;
--breakpoint-lg: 992px; --breakpoint-lg: 992px;
--breakpoint-xl: 1200px; --breakpoint-xl: 1200px;
--font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
} }
@ -54,7 +54,7 @@ article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
line-height: 1.5; line-height: 1.5;
@ -228,9 +228,8 @@ button {
border-radius: 0; border-radius: 0;
} }
button:focus { button:focus:not(:focus-visible) {
outline: 1px dotted; outline: 0;
outline: 5px auto -webkit-focus-ring-color;
} }
input, input,
@ -2241,6 +2240,11 @@ textarea.form-control {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
.form-row > .col > .valid-tooltip,
.form-row > [class*="col-"] > .valid-tooltip {
left: 5px;
}
.was-validated :valid ~ .valid-feedback, .was-validated :valid ~ .valid-feedback,
.was-validated :valid ~ .valid-tooltip, .was-validated :valid ~ .valid-tooltip,
.is-valid ~ .valid-feedback, .is-valid ~ .valid-feedback,
@ -2270,7 +2274,7 @@ textarea.form-control {
.was-validated .custom-select:valid, .custom-select.is-valid { .was-validated .custom-select:valid, .custom-select.is-valid {
border-color: #28a745; border-color: #28a745;
padding-right: calc(0.75em + 2.3125rem); padding-right: calc(0.75em + 2.3125rem);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 0.75rem center/8px 10px no-repeat, #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) no-repeat;
} }
.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus { .was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {
@ -2342,6 +2346,11 @@ textarea.form-control {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
.form-row > .col > .invalid-tooltip,
.form-row > [class*="col-"] > .invalid-tooltip {
left: 5px;
}
.was-validated :invalid ~ .invalid-feedback, .was-validated :invalid ~ .invalid-feedback,
.was-validated :invalid ~ .invalid-tooltip, .was-validated :invalid ~ .invalid-tooltip,
.is-invalid ~ .invalid-feedback, .is-invalid ~ .invalid-feedback,
@ -2371,7 +2380,7 @@ textarea.form-control {
.was-validated .custom-select:invalid, .custom-select.is-invalid { .was-validated .custom-select:invalid, .custom-select.is-invalid {
border-color: #dc3545; border-color: #dc3545;
padding-right: calc(0.75em + 2.3125rem); padding-right: calc(0.75em + 2.3125rem);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 0.75rem center/8px 10px no-repeat, #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) no-repeat;
} }
.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus { .was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {
@ -3388,7 +3397,7 @@ input[type="button"].btn-block {
.dropdown-item:hover, .dropdown-item:focus { .dropdown-item:hover, .dropdown-item:focus {
color: #16181b; color: #16181b;
text-decoration: none; text-decoration: none;
background-color: #f8f9fa; background-color: #e9ecef;
} }
.dropdown-item.active, .dropdown-item:active { .dropdown-item.active, .dropdown-item:active {
@ -3398,7 +3407,7 @@ input[type="button"].btn-block {
} }
.dropdown-item.disabled, .dropdown-item:disabled { .dropdown-item.disabled, .dropdown-item:disabled {
color: #6c757d; color: #adb5bd;
pointer-events: none; pointer-events: none;
background-color: transparent; background-color: transparent;
} }
@ -3597,12 +3606,6 @@ input[type="button"].btn-block {
z-index: 4; z-index: 4;
} }
.input-group > .form-control:not(:last-child),
.input-group > .custom-select:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-group > .form-control:not(:first-child), .input-group > .form-control:not(:first-child),
.input-group > .custom-select:not(:first-child) { .input-group > .custom-select:not(:first-child) {
border-top-left-radius: 0; border-top-left-radius: 0;
@ -3617,14 +3620,23 @@ input[type="button"].btn-block {
} }
.input-group > .custom-file:not(:last-child) .custom-file-label, .input-group > .custom-file:not(:last-child) .custom-file-label,
.input-group > .custom-file:not(:last-child) .custom-file-label::after { .input-group > .custom-file:not(:first-child) .custom-file-label {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.input-group:not(.has-validation) > .form-control:not(:last-child),
.input-group:not(.has-validation) > .custom-select:not(:last-child),
.input-group:not(.has-validation) > .custom-file:not(:last-child) .custom-file-label::after {
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
.input-group > .custom-file:not(:first-child) .custom-file-label { .input-group.has-validation > .form-control:nth-last-child(n + 3),
border-top-left-radius: 0; .input-group.has-validation > .custom-select:nth-last-child(n + 3),
border-bottom-left-radius: 0; .input-group.has-validation > .custom-file:nth-last-child(n + 3) .custom-file-label::after {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
} }
.input-group-prepend, .input-group-prepend,
@ -3727,8 +3739,10 @@ input[type="button"].btn-block {
.input-group > .input-group-prepend > .btn, .input-group > .input-group-prepend > .btn,
.input-group > .input-group-prepend > .input-group-text, .input-group > .input-group-prepend > .input-group-text,
.input-group > .input-group-append:not(:last-child) > .btn, .input-group:not(.has-validation) > .input-group-append:not(:last-child) > .btn,
.input-group > .input-group-append:not(:last-child) > .input-group-text, .input-group:not(.has-validation) > .input-group-append:not(:last-child) > .input-group-text,
.input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .btn,
.input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .input-group-text,
.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), .input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),
.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { .input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {
border-top-right-radius: 0; border-top-right-radius: 0;
@ -3825,7 +3839,7 @@ input[type="button"].btn-block {
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
content: ""; content: "";
background: no-repeat 50% / 50% 50%; background: 50% / 50% 50% no-repeat;
} }
.custom-checkbox .custom-control-label::before { .custom-checkbox .custom-control-label::before {
@ -3914,7 +3928,7 @@ input[type="button"].btn-block {
line-height: 1.5; line-height: 1.5;
color: #495057; color: #495057;
vertical-align: middle; vertical-align: middle;
background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px; background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 0.75rem center/8px 10px no-repeat;
border: 1px solid #ced4da; border: 1px solid #ced4da;
border-radius: 0.25rem; border-radius: 0.25rem;
-webkit-appearance: none; -webkit-appearance: none;
@ -3983,6 +3997,7 @@ input[type="button"].btn-block {
width: 100%; width: 100%;
height: calc(1.5em + 0.75rem + 2px); height: calc(1.5em + 0.75rem + 2px);
margin: 0; margin: 0;
overflow: hidden;
opacity: 0; opacity: 0;
} }
@ -4012,6 +4027,7 @@ input[type="button"].btn-block {
z-index: 1; z-index: 1;
height: calc(1.5em + 0.75rem + 2px); height: calc(1.5em + 0.75rem + 2px);
padding: 0.375rem 0.75rem; padding: 0.375rem 0.75rem;
overflow: hidden;
font-weight: 400; font-weight: 400;
line-height: 1.5; line-height: 1.5;
color: #495057; color: #495057;
@ -4048,7 +4064,7 @@ input[type="button"].btn-block {
} }
.custom-range:focus { .custom-range:focus {
outline: none; outline: 0;
} }
.custom-range:focus::-webkit-slider-thumb { .custom-range:focus::-webkit-slider-thumb {
@ -4243,11 +4259,8 @@ input[type="button"].btn-block {
border-bottom: 1px solid #dee2e6; border-bottom: 1px solid #dee2e6;
} }
.nav-tabs .nav-item {
margin-bottom: -1px;
}
.nav-tabs .nav-link { .nav-tabs .nav-link {
margin-bottom: -1px;
border: 1px solid transparent; border: 1px solid transparent;
border-top-left-radius: 0.25rem; border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem; border-top-right-radius: 0.25rem;
@ -4403,8 +4416,12 @@ input[type="button"].btn-block {
height: 1.5em; height: 1.5em;
vertical-align: middle; vertical-align: middle;
content: ""; content: "";
background: no-repeat center center; background: 50% / 100% 100% no-repeat;
background-size: 100% 100%; }
.navbar-nav-scroll {
max-height: 75vh;
overflow-y: auto;
} }
@media (max-width: 575.98px) { @media (max-width: 575.98px) {
@ -4438,6 +4455,9 @@ input[type="button"].btn-block {
-ms-flex-wrap: nowrap; -ms-flex-wrap: nowrap;
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.navbar-expand-sm .navbar-nav-scroll {
overflow: visible;
}
.navbar-expand-sm .navbar-collapse { .navbar-expand-sm .navbar-collapse {
display: -ms-flexbox !important; display: -ms-flexbox !important;
display: flex !important; display: flex !important;
@ -4480,6 +4500,9 @@ input[type="button"].btn-block {
-ms-flex-wrap: nowrap; -ms-flex-wrap: nowrap;
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.navbar-expand-md .navbar-nav-scroll {
overflow: visible;
}
.navbar-expand-md .navbar-collapse { .navbar-expand-md .navbar-collapse {
display: -ms-flexbox !important; display: -ms-flexbox !important;
display: flex !important; display: flex !important;
@ -4522,6 +4545,9 @@ input[type="button"].btn-block {
-ms-flex-wrap: nowrap; -ms-flex-wrap: nowrap;
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.navbar-expand-lg .navbar-nav-scroll {
overflow: visible;
}
.navbar-expand-lg .navbar-collapse { .navbar-expand-lg .navbar-collapse {
display: -ms-flexbox !important; display: -ms-flexbox !important;
display: flex !important; display: flex !important;
@ -4564,6 +4590,9 @@ input[type="button"].btn-block {
-ms-flex-wrap: nowrap; -ms-flex-wrap: nowrap;
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.navbar-expand-xl .navbar-nav-scroll {
overflow: visible;
}
.navbar-expand-xl .navbar-collapse { .navbar-expand-xl .navbar-collapse {
display: -ms-flexbox !important; display: -ms-flexbox !important;
display: flex !important; display: flex !important;
@ -4608,6 +4637,10 @@ input[type="button"].btn-block {
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.navbar-expand .navbar-nav-scroll {
overflow: visible;
}
.navbar-expand .navbar-collapse { .navbar-expand .navbar-collapse {
display: -ms-flexbox !important; display: -ms-flexbox !important;
display: flex !important; display: flex !important;
@ -4972,17 +5005,12 @@ input[type="button"].btn-block {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
.breadcrumb-item {
display: -ms-flexbox;
display: flex;
}
.breadcrumb-item + .breadcrumb-item { .breadcrumb-item + .breadcrumb-item {
padding-left: 0.5rem; padding-left: 0.5rem;
} }
.breadcrumb-item + .breadcrumb-item::before { .breadcrumb-item + .breadcrumb-item::before {
display: inline-block; float: left;
padding-right: 0.5rem; padding-right: 0.5rem;
color: #6c757d; color: #6c757d;
content: "/"; content: "/";
@ -5465,8 +5493,8 @@ a.badge-dark:focus, a.badge-dark.focus {
} }
.progress-bar-animated { .progress-bar-animated {
-webkit-animation: progress-bar-stripes 1s linear infinite; -webkit-animation: 1s linear infinite progress-bar-stripes;
animation: progress-bar-stripes 1s linear infinite; animation: 1s linear infinite progress-bar-stripes;
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
@ -6145,7 +6173,7 @@ a.close.disabled {
z-index: 1070; z-index: 1070;
display: block; display: block;
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
line-height: 1.5; line-height: 1.5;
@ -6258,7 +6286,7 @@ a.close.disabled {
z-index: 1060; z-index: 1060;
display: block; display: block;
max-width: 276px; max-width: 276px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
line-height: 1.5; line-height: 1.5;
@ -6546,7 +6574,7 @@ a.close.disabled {
display: inline-block; display: inline-block;
width: 20px; width: 20px;
height: 20px; height: 20px;
background: no-repeat 50% / 100% 100%; background: 50% / 100% 100% no-repeat;
} }
.carousel-control-prev-icon { .carousel-control-prev-icon {
@ -6635,8 +6663,8 @@ a.close.disabled {
border: 0.25em solid currentColor; border: 0.25em solid currentColor;
border-right-color: transparent; border-right-color: transparent;
border-radius: 50%; border-radius: 50%;
-webkit-animation: spinner-border .75s linear infinite; -webkit-animation: .75s linear infinite spinner-border;
animation: spinner-border .75s linear infinite; animation: .75s linear infinite spinner-border;
} }
.spinner-border-sm { .spinner-border-sm {
@ -6677,8 +6705,8 @@ a.close.disabled {
background-color: currentColor; background-color: currentColor;
border-radius: 50%; border-radius: 50%;
opacity: 0; opacity: 0;
-webkit-animation: spinner-grow .75s linear infinite; -webkit-animation: .75s linear infinite spinner-grow;
animation: spinner-grow .75s linear infinite; animation: .75s linear infinite spinner-grow;
} }
.spinner-grow-sm { .spinner-grow-sm {
@ -6686,6 +6714,14 @@ a.close.disabled {
height: 1rem; height: 1rem;
} }
@media (prefers-reduced-motion: reduce) {
.spinner-border,
.spinner-grow {
-webkit-animation-duration: 1.5s;
animation-duration: 1.5s;
}
}
.align-baseline { .align-baseline {
vertical-align: baseline !important; vertical-align: baseline !important;
} }
@ -7954,7 +7990,6 @@ button.bg-dark:focus {
.user-select-all { .user-select-all {
-webkit-user-select: all !important; -webkit-user-select: all !important;
-moz-user-select: all !important; -moz-user-select: all !important;
-ms-user-select: all !important;
user-select: all !important; user-select: all !important;
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
header { header {
height: 4rem; height : 4rem;
background-color: #fff; background-color: #fff;
} }
@ -15,6 +15,10 @@ nav {
margin-bottom: .5rem; margin-bottom: .5rem;
} }
#profile_button_panel {
z-index: 1500;
}
.h-divider { .h-divider {
border-bottom: 4px solid #123EAB; border-bottom: 4px solid #123EAB;
} }
@ -24,45 +28,46 @@ nav {
} }
.h-divider-title-left { .h-divider-title-left {
max-width: 0; max-width : 0;
max-height: 0; max-height : 0;
border: 1rem solid transparent; border : 1rem solid transparent;
border-right: 1rem solid #123EAB; border-right : 1rem solid #123EAB;
border-bottom: 1rem solid #123EAB; border-bottom: 1rem solid #123EAB;
} }
.h-divider-title-right { .h-divider-title-right {
max-width: 0; max-width : 0;
max-height: 0; max-height : 0;
border: 1rem solid transparent; border : 1rem solid transparent;
border-left: 1rem solid #123EAB; border-left : 1rem solid #123EAB;
border-bottom: 1rem solid #123EAB; border-bottom: 1rem solid #123EAB;
} }
.catalog_button { .catalog_button {
width: 100%; width : 100%;
height: 2rem; height : 2rem;
border-radius: 3px; border-radius : 3px;
background-clip: unset; background-clip : unset;
border: 1px solid #a39bb1; border : 1px solid #a39bb1;
/* background-color: #123EAB; */ /* background-color: #123EAB; */
background-color: #e4e2ea; background-color : #e4e2ea;
} }
.catalog_button:hover { .catalog_button:hover {
border: 1px solid #c0b7d0; border : 1px solid #c0b7d0;
/* background-color: #1b4bc4; */ /* background-color: #1b4bc4; */
background-color: #ebe9f2; background-color : #ebe9f2;
} }
.catalog_button:active, .catalog_button:focus { .catalog_button:active,
border: 1px solid #7b7587; .catalog_button:focus {
border : 1px solid #7b7587;
/* background-color: #402d82; */ /* background-color: #402d82; */
background-color: #d7d4dd; background-color : #d7d4dd;
} }
.catalog_button p { .catalog_button p {
font-size: 85%; font-size : 85%;
line-height: 100%; line-height: 100%;
} }
@ -71,25 +76,25 @@ nav {
} }
.catalog_search { .catalog_search {
padding: 0.8rem; padding : 0.8rem;
/* border-radius: 0px 5px 5px 5px; */ /* border-radius: 0px 5px 5px 5px; */
border-radius: 5px; border-radius : 5px;
background-color: #123EAB; background-color: #123EAB;
} }
#searchPanel label { #searchPanel label {
display: inline-block; display : inline-block;
cursor: pointer; cursor : pointer;
user-select: none; user-select : none;
font-size: smaller; font-size : smaller;
border-radius: 5px 5px 0px 0px; border-radius : 5px 5px 0px 0px;
color: #000; color : #000;
border-bottom: none; border-bottom : none;
background-color: #e4e2ea; background-color: #e4e2ea;
} }
#searchPanel label:hover { #searchPanel label:hover {
border-bottom: none; border-bottom : none;
background-color: #ebe9f2; background-color: #ebe9f2;
} }
@ -97,101 +102,97 @@ nav {
display: none; display: none;
} }
#searchPanel input[type=radio]+label:active, #searchPanel input[type=radio]:checked+label:active { #searchPanel input[type=radio]+label:active,
border-bottom: none; #searchPanel input[type=radio]:checked+label:active {
border-bottom : none;
background-color: #d7d4dd; background-color: #d7d4dd;
} }
#searchPanel input[type=radio]:checked+label, #searchPanel input[type=radio]:checked:hover+label, #searchPanel input[type=radio]:checked:active+label { #searchPanel input[type=radio]:checked+label,
color: #ffffff; #searchPanel input[type=radio]:checked:hover+label,
#searchPanel input[type=radio]:checked:active+label {
color : #ffffff;
background-color: #123EAB; background-color: #123EAB;
} }
#searchPanel input[type=radio]:disabled+label { #searchPanel input[type=radio]:disabled+label {
background: #241c3d; background: #241c3d;
color: #c4c4c4; color : #c4c4c4;
} }
.catalog_search_line { .catalog_search_line {
height: 2rem; height : 2rem;
margin: 0 .8rem 0 0; margin : 0 .8rem 0 0;
font-size: 85%; font-size : 85%;
line-height: 100%; line-height : 100%;
border-radius: 3px; border-radius : 3px;
border: 1px solid #a39bb1; border : 1px solid #a39bb1;
background-color: #fbf9ff !important; background-color: #fbf9ff !important;
} }
.catalog_search_button { .catalog_search_button {
height: 2rem; height : 2rem;
margin: 0 0 0 .8rem; margin : 0 0 0 .8rem;
font-size: 85%; font-size : 85%;
line-height: 100%; line-height : 100%;
border-radius: 3px; border-radius : 3px;
border: 1px solid #a39bb1; border : 1px solid #a39bb1;
background-color: #e4e2ea; background-color: #e4e2ea;
} }
.catalog_search_button:hover { .catalog_search_button:hover {
border: 1px solid #c0b7d0; border : 1px solid #c0b7d0;
background-color: #ebe9f2; background-color: #ebe9f2;
} }
.catalog_search_button:active { .catalog_search_button:active {
border: 1px solid #7b7587; border : 1px solid #7b7587;
background-color: #d7d4dd; background-color: #d7d4dd;
} }
@media (max-width: 400px) { @media (max-width: 400px) {
.logotype {
display: none !important;
}
.catalog_search { .catalog_search {
flex-direction: column; flex-direction: column;
} }
.catalog_search_line {
max-width: 100%;
border-radius: 3px 3px 0px 0px;
}
.catalog_search_button { .catalog_search_button {
border-radius: 0px 0px 3px 3px; margin: .8rem 0 0 0;
height: auto;
line-height: 200%;
} }
} }
/* Малые девайсы («ландшафтные телефоны», >= 576px) */ /* Малые девайсы («ландшафтные телефоны», >= 576px) */
@media (max-width: 576px) { @media (max-width: 576px) {
.tagline {
#logo,
.tagline,
.h-divider {
display: none !important; display: none !important;
} }
#searchPanel label { #searchPanel label {
width: 100%; width : 100%;
text-align: center; text-align: center;
} }
#searchPanel label:not(:nth-child(2)) {
border-radius: 0;
}
.catalog_search {
border-radius: 0px 0px 5px 5px;
}
} }
/* Средние девайсы («таблетки», >= 768px) */ /* Средние девайсы («таблетки», >= 768px) */
@media (max-width: 768px) {} @media (max-width: 768px) {
.dropdownMenuButton_column {
display: none !important;
}
}
/* Большие девайсы (десктопы, >= 992px) */ /* Большие девайсы (десктопы, >= 992px) */
@media (max-width: 992px) { @media (max-width: 992px) {
.h-divider {
margin-top: 15px;
}
.h-divider div {
display: none !important;
}
.dropdownMenuButton_column { .dropdownMenuButton_column {
width: max-content; width: max-content;
} }
#dropdownMenuButton p { #dropdownMenuButton p {
display: none; display: none;
} }
@ -199,4 +200,8 @@ nav {
/* Экстрабольшие девайсы (большие десктопы, >= 1200px) */ /* Экстрабольшие девайсы (большие десктопы, >= 1200px) */
@media (max-width: 1200px) {} @media (max-width: 1200px) {
.h-title>* {
display: none !important;
}
}

View File

@ -1,13 +1,17 @@
@font-face { @font-face {
font-family: 'Gilroy'; font-family: 'Gilroy';
font-weight: 600; font-weight: 600;
src: local('/fonts/Gilroy/gilroy-semibold.ttf'), url('/fonts/Gilroy/gilroy-semibold.ttf'); src : local('/fonts/Gilroy/gilroy-semibold.ttf'), url('/fonts/Gilroy/gilroy-semibold.ttf');
} }
* { * {
font-family: 'Open Sans'; font-family: 'Open Sans';
} }
*:focus-visible {
outline: none;
}
a { a {
cursor: pointer; cursor: pointer;
} }
@ -25,36 +29,95 @@ button {
} }
body { body {
min-height: 100vh; min-height : 100vh;
display: flex; display : flex;
flex-direction: column; flex-direction: column;
overflow-x: hidden; overflow-x : hidden;
} }
main { main {
flex-grow: 1; flex-grow : 1;
background-color: #f0eefb; background-color: #f0eefb;
} }
.button_clean, .button_clean:hover, .button_clean:focus, .button_clean:active { .inactive,
outline: none !important; .inactive:hover,
box-shadow: none !important; .inactive:focus,
} .inactive:active {
.inactive, .inactive:hover, .inactive:focus, .inactive:active {
pointer-events: none; pointer-events: none;
} }
.button_blue { .button_clean,
.button_clean:hover,
.button_clean:focus,
.button_clean:active,
.button_clean_full,
.button_clean_full:hover,
.button_clean_full:focus,
.button_clean_full:active {
outline : none;
box-shadow: none;
}
.button_clean_full,
.button_clean_full:hover,
.button_clean_full:focus,
.button_clean_full:active {
border : none;
background: none;
}
.button_blue_simple {
color : #eee;
background-color: #123EAB; background-color: #123EAB;
transition : 0s;
}
.button_blue_simple:hover {
color : #fff;
transition: 0s;
}
.button_blue_simple:active {
color : #ddd;
background-color: #391e97;
transition : 0s;
}
.button_blue {
color : #eee;
background-color: #123EAB;
transition : 0s;
} }
.button_blue:hover { .button_blue:hover {
background-color: #1b4bc4; color : #fff;
background-color: #244db5;
transition : 0s;
} }
.button_blue:active { .button_blue:active {
background-color: #402d82; color : #ddd;
background-color: #391e97;
transition : 0s;
}
.button_red {
color : #eee;
background-color: #ab1212;
transition : 0s;
}
.button_red:hover {
color : #fff;
background-color: #b52424;
transition : 0s;
}
.button_red:active {
color : #ddd;
background-color: #971e1e;
transition : 0s;
} }
.button_white { .button_white {
@ -63,10 +126,31 @@ main {
.button_white:hover { .button_white:hover {
background-color: #eaebee; background-color: #eaebee;
transition : 0s;
} }
.button_white:active { .button_white:active {
background-color: #cfd3dd; background-color: #cfd3dd;
transition : 0s;
}
.button_white_hover {
background-color: #eaebee !important;
transition : 0s;
}
.button_grey {
background-color: #b4bbcc;
}
.button_grey:hover {
background-color: #a6aebf;
transition : 0s;
}
.button_grey:active {
background-color: #8c94a8;
transition : 0s;
} }
.d-inline-block { .d-inline-block {

View File

@ -0,0 +1,19 @@
#notifications_popup_wrap>.notification {
min-height : 100px;
border : 1px solid #a39bb1;
background-color: #fff;
opacity: 0;
transition: .3s;
}
#notifications_popup_wrap {
z-index : 100;
bottom : 0;
position: fixed;
overflow: hidden;
}
#notification_button_panel {
z-index: 2500;
min-width: 250px;
}

View File

@ -1,3 +1,33 @@
#page_product nav > div, #page_product article > div { #page_product nav>div,
#page_product article>div {
background-color: #fff; background-color: #fff;
}
#page_product #product_slider>.product_slider_image>div,
#page_product #product_slider>.product_slider_image>input {
display: none;
}
#page_product #product_slider>.product_slider_image>input:checked+div {
display: block;
}
#page_product #product_slider>.product_slider_image {
max-width: 25vw;
}
#page_product #product_slider>.product_slider_preview>label {
border : none;
overflow: hidden;
}
#page_product #product_slider>.product_slider_preview>label:hover>img {
cursor: pointer;
filter: brightness(0.9) contrast(1.1);
}
#page_product #product_slider>.product_slider_preview>label:active>img,
#page_product #product_slider>.product_slider_preview>label:focus>img {
cursor: pointer;
filter: brightness(0.8) contrast(1.3);
} }

View File

@ -1,3 +1,72 @@
#page_profile nav > div, #page_profile article > div { #page_profile nav>div,
#page_profile article>div {
background-color: #fff; background-color: #fff;
} }
#page_profile [id^=profile_panel_]>.profile_panel_content {
flex-grow : 1;
border-radius: 0 3px 3px 3px;
}
#page_profile [id^=profile_panel_]>.profile_panel_content>div,
#page_profile [id^=profile_panel_]>.profile_panel_content>input {
display: none;
}
#page_profile [id^=profile_panel_]>.profile_panel_content>input:checked+div {
display: block;
}
#page_profile [id^=profile_panel_]>.profile_panel_content>div>.header_blue {
background-color: #123EAB;
}
#page_profile [id^=profile_panel_]>.profile_panel_content>div>.header_blue~.row:nth-child(2n) {
background-color: #f0f5ff;
}
#page_profile [id^=profile_panel_]>.profile_panel_content>div>.header_blue~.row:nth-child(2n + 1) {
background-color: #e7edf9;
}
/* Экстрабольшие девайсы (большие десктопы, >= 1200px) */
@media (max-width: 1199px) {
#page_profile nav dl>dt>a>i {
display: none !important;
}
}
/* Большие девайсы (десктопы, >= 992px) */
@media (max-width: 991px) {
#page_profile nav dl>dt>a>span {
display: none !important;
}
#page_profile nav dl>dt>a>i {
display: inherit !important;
}
}
/* Средние девайсы («таблетки», >= 768px) */
/* @media (max-width: 768px) {} */
/* Малые девайсы («ландшафтные телефоны», >= 576px) */
@media (max-width: 576px) {
#page_profile article h4 {
display: none !important;
}
#page_profile #profile_panel_monitoring>.profile_panel_content>div>div>div:first-child {
display: none !important;
}
#page_profile #profile_panel_monitoring>.profile_panel_content>div>div:last-child>div:first-child {
display: flex !important;
}
}
/* @media (max-width: 400px) {} */

View File

@ -1,3 +1,15 @@
#page_search nav > div, #page_search article > div { #page_search nav > div, #page_search article > div {
background-color: #fff; background-color: #fff;
}
#page_search .search_card:hover {
background-color: #eaeaf0;
}
#page_search .search_card:hover img {
border: 1px solid #e1e1ea;
}
#page_search .search_card .search_card_cart_icon {
font-size: larger;
} }

View File

@ -0,0 +1 @@
/supplies

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Some files were not shown because too many files have changed in this diff Show More