From 140e40e79a82f0423161edd75982bd3af74045cd Mon Sep 17 00:00:00 2001 From: Arsen Mirzaev Tatyano-Muradovich Date: Tue, 22 Oct 2024 19:28:49 +0300 Subject: [PATCH] cart + interfase + reservation + currencies --- README.md | 72 +-- .../arming_bot/system/controllers/cart.php | 114 ++++- .../arming_bot/system/controllers/catalog.php | 18 +- .../arming_bot/system/controllers/core.php | 36 +- mirzaev/arming_bot/system/models/account.php | 8 +- mirzaev/arming_bot/system/models/cart.php | 252 ++++++++++- mirzaev/arming_bot/system/models/catalog.php | 4 +- mirzaev/arming_bot/system/models/core.php | 4 +- mirzaev/arming_bot/system/models/entry.php | 44 +- .../system/models/enumerations/currency.php | 69 +++ .../system/models/enumerations/language.php | 10 +- mirzaev/arming_bot/system/models/product.php | 30 +- .../arming_bot/system/models/reservation.php | 39 ++ mirzaev/arming_bot/system/models/session.php | 125 +----- mirzaev/arming_bot/system/models/settings.php | 4 +- .../system/models/traits/buffer.php | 13 +- .../arming_bot/system/models/traits/cart.php | 151 +++++++ .../system/models/traits/document.php | 3 +- mirzaev/arming_bot/system/public/index.php | 3 +- mirzaev/arming_bot/system/public/js/cart.js | 418 +++++++++++++++++- .../public/themes/default/css/catalog.css | 43 +- .../public/themes/default/css/icons/minus.css | 12 + .../public/themes/default/css/icons/plus.css | 24 + .../system/public/themes/default/css/main.css | 4 +- mirzaev/arming_bot/system/views/templater.php | 24 +- .../default/catalog/elements/categories.html | 4 +- .../default/catalog/elements/filters.html | 4 +- .../default/catalog/elements/products.html | 28 +- .../default/catalog/elements/search.html | 2 +- .../views/themes/default/catalog/page.html | 2 + 30 files changed, 1294 insertions(+), 270 deletions(-) create mode 100644 mirzaev/arming_bot/system/models/enumerations/currency.php create mode 100755 mirzaev/arming_bot/system/models/reservation.php create mode 100755 mirzaev/arming_bot/system/models/traits/cart.php create mode 100755 mirzaev/arming_bot/system/public/themes/default/css/icons/minus.css create mode 100755 mirzaev/arming_bot/system/public/themes/default/css/icons/plus.css diff --git a/README.md b/README.md index bacb17b..28a568f 100755 --- a/README.md +++ b/README.md @@ -7,35 +7,35 @@ Basis for developing chat-robots with "Web App" technology for Telegram ### AnangoDB 1. Create a Graph with the specified values -**Name:** catalog
-
-**edgeDefinition:** entry
-**fromCollections:** categoy, product
+**Name:** catalog
+
+**edgeDefinition:** entry
+**fromCollections:** categoy, product
**toCollections:** category 2. Create a Graph with the specified values -**Name:** sessions
-
-**edgeDefinition:** connect
-**fromCollections:** account
+**Name:** sessions
+
+**edgeDefinition:** connect
+**fromCollections:** account
**toCollections:** session 3. Create indexes for the "product" collection -**Type:** "Inverted Index"
-**Fields:** name.ru
-**Analyzer:** "text_ru"
-**Search field:** true
-**Name:** name_ru
-
-*Add indexes for all search parameters and for all languages (search language is selected based on the user's language,
-otherwise from the default language specified in the active settings from **settings** collection document)*
-
-*See fields in the `mirzaev/arming_bot/models/product`
+**Type:** "Inverted Index"
+**Fields:** name.ru
+**Analyzer:** "text_ru"
+**Search field:** true
+**Name:** name_ru
+
+*Add indexes for all search parameters and for all languages (search language is selected based on the user's language,
+otherwise from the default language specified in the active settings from **settings** collection document)*
+
+*See fields in the `mirzaev/arming_bot/models/product`
**name.ru**, **description.ru** and **compatibility.ru*** 4. Create a View with the specified values -**type:** search-alias (you can also use "arangosearch")
-**name:** **product**s_search
+**type:** search-alias (you can also use "arangosearch")
+**name:** **product**s_search
**indexes:** ```json "indexes": [ @@ -68,27 +68,39 @@ location ~ \.php$ { 1. Execute: `sudo cp telegram-huesos.service /etc/systemd/system/telegram-huesos.service` -*before you execute the command think about **what it does** and whether the **paths** are specified correctly*
+*before you execute the command think about **what it does** and whether the **paths** are specified correctly*
*the configuration file is very simple and you can remake it for any alternative to SystemD that you like* ## Settings -Settings of chat-robot and Web App
-
+Settings of chat-robot and Web App
+
Make sure you have a **settings** collection (can be created automatically) and at least one document with the "status" parameter set to "active" ```json { - "status": "active" + "status": "active", + "project": { + "name": "NAME_OF_THE_PROJECT" + }, + "language": "en", + "currency": "usd" } ``` -### language -Language for system messages if user language could not be determined
-
-**Value:** en +### Language +Language for render of interface, if account or session language is not initialized
+
+**Value:** en
+**⚠️ The value will be converted to an instance of enumeration** `mirzaev\arming_bot\models\enumerations\language` + +### Currency +Currency for calculations and render of interface, if account or session currency is not initialized
+
+**Value:** usd
+**⚠️ The value will be converted to an instance of enumeration** `mirzaev\arming_bot\models\enumerations\currency` ## Suspensions -System of suspensions of chat-robot and Web App
-
+System of suspensions of chat-robot and Web App
+
Make sure you have a **suspension** collection (can be created automatically) ```json { diff --git a/mirzaev/arming_bot/system/controllers/cart.php b/mirzaev/arming_bot/system/controllers/cart.php index a3b539a..a166020 100755 --- a/mirzaev/arming_bot/system/controllers/cart.php +++ b/mirzaev/arming_bot/system/controllers/cart.php @@ -32,22 +32,28 @@ final class cart extends core ]; /** - * Write or delete from the cart + * Product + * + * Write or delete the product in the cart * * @param array $parameters Parameters of the request (POST + GET) + * + * @todo + * 1. Add a limit on adding products to the cart based on the number of products in stock */ - public function write(array $parameters = []): ?string + public function product(array $parameters = []): ?string { if ($_SERVER['REQUEST_METHOD'] === 'POST') { // POST request - // The cart contains the product? - $status = false; + // Declaring of the buffer with amount of the product in the cart + $amount = 0; // Validating @todo add throwing errors - if (!empty($parameters['product']) && preg_match('/[\d]+/', urldecode($parameters['product']), $matches)) $product = (int) $matches[0]; + $identifier = null; + if (!empty($parameters['identifier']) && preg_match('/[\d]+/', urldecode($parameters['identifier']), $matches)) $identifier = (int) $matches[0]; - if (isset($product)) { + if (isset($identifier)) { // Received and validated identfier of the product // Search for the product @@ -55,34 +61,98 @@ final class cart extends core filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true", sort: 'd.created DESC', amount: 1, - parameters: ['identifier' => $product], + parameters: ['identifier' => $identifier], errors: $this->errors['cart'] ); if ($product instanceof product) { // Initialized the product - // Initializing of the cart - $cart = $this->session->cart(errors: $this->errors['cart']); + // Initializing the buffer with amount of the product in the cart + $amount = $this->cart->count(product: $product, limit: 100, errors: $this->errors['cart']) ?? 0; - if ($cart instanceof model) { + if ($this->cart instanceof model) { // Initialized the cart - if (0 < $amount = $cart->has($product, errors: $this->errors['cart'])) { - // The cart contains the product + // Validating @todo add throwing errors + $type = null; + if (!empty($parameters['type']) && preg_match('/[\w]+/', urldecode($parameters['type']), $matches)) $type = $matches[0]; - // temporary - /* $cart->disconnect($product, errors: $this->errors['cart']); */ - $cart->cancel($product, errors: $this->errors['cart']); + if (isset($type)) { + // Received and validated type of action with the product - $status = false; - } else { - // The cart not contains the product + if ($type === 'toggle') { + // Write the product to the cart if is not in the cart and vice versa - // temporary - $cart->connect($product, errors: $this->errors['cart']); + if ($amount > 0) { + // The cart has the product - $status = true; + // Deleting the product from the cart + $this->cart->delete(product: $product, amount: $amount, errors: $this->errors['cart']); + + // Reinitializing the buffer with amount of the product in the cart + $amount = 0; + } else { + // The cart has no the product + + // Writing the product to the cart + $this->cart->write(product: $product, amount: 1, errors: $this->errors['cart']); + + // Reinitializing the buffer with amount of the product in the cart + $amount = 1; + } + } else { + // Received not the "toggle" command + + // Validating @todo add throwing errors + $_amount = null; + if (!empty($parameters['amount']) && preg_match('/[\d]+/', urldecode($parameters['amount']), $matches)) $_amount = (int) $matches[0]; + + if (isset($_amount)) { + // Received and validated amount parameter for action with the product + + if ($type === 'write') { + // Increase amount of the product in the cart + + if (101 > $amount += $_amount) { + // Validated amount to wrting + + // Writing the product to the cart + $this->cart->write(product: $product, amount: $_amount, errors: $this->errors['cart']); + } + } else if ($type === 'delete') { + // Decrease amount of the product in the cart + + if (-1 < $amount -= $_amount) { + // Validated amount to deleting + + // Deleting the product from the cart + $this->cart->delete(product: $product, amount: $_amount, errors: $this->errors['cart']); + } + } else if ($type === 'set') { + // Set amount of the product in the cart + + if ($_amount > -1 && $_amount < 101) { + // Validated amount to setting + + if ($_amount > $amount) { + // Requested amount more than actual amount of the product in the cart + + // Writing the product from the cart + $this->cart->write(product: $product, amount: $_amount - $amount, errors: $this->errors['cart']); + } else { + // Requested amount less than actual amount of the product in the cart + + // Deleting the product from the cart + $this->cart->delete(product: $product, amount: $amount - $_amount, errors: $this->errors['cart']); + } + + // Reinitializing the buffer with amount of the product in the cart + $amount = $_amount; + } + } + } + } } } } @@ -99,7 +169,7 @@ final class cart extends core // Generating the reponse echo json_encode( [ - 'status' => $status ?? false, + 'amount' => $amount, // $amount does not store a real value, but is calculated without a repeated request to ArangoDB 'errors' => $this->errors ] ); diff --git a/mirzaev/arming_bot/system/controllers/catalog.php b/mirzaev/arming_bot/system/controllers/catalog.php index e95eb97..8f6f5e7 100755 --- a/mirzaev/arming_bot/system/controllers/catalog.php +++ b/mirzaev/arming_bot/system/controllers/catalog.php @@ -53,9 +53,10 @@ final class catalog extends core filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true", sort: 'd.created DESC', amount: 1, - return: '{name: d.name.@language, description: d.description.@language, cost: d.cost, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, images: d.images[*].storage}', - parameters: ['identifier' => $product], + return: '{name: d.name.@language, description: d.description.@language, cost: d.cost, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, cost: d.cost.@currency, images: d.images[*].storage}', language: $this->language, + currency: $this->currency, + parameters: ['identifier' => $product], errors: $this->errors['catalog'] )[0]?->getAll() ?? null; } @@ -251,6 +252,9 @@ final class catalog extends core $entries = entry::search( document: $category, amount: 50, + categories_merge: 'name: v.name.@language', + /* products_merge: 'DISTINCT MERGE(d, {name: d.name.@language, description: d.description.@language, compatibility: d.compatibility.@language, brand: d.brand.@language, cost: d.cost.@currency})', */ + parameters: ['language' => $this->language->name], errors: $this->errors['catalog'] ); @@ -284,7 +288,12 @@ final class catalog extends core // Not received identifier of the category // search for root ascendants categories - $this->view->categories = entry::ascendants(descendant: new category, errors: $this->errors['catalog']) ?? null; + $this->view->categories = entry::ascendants( + descendant: new category, + return: 'DISTINCT MERGE(ascendant, { name: ascendant.name.@language})', + parameters: ['language' => $this->language->name], + errors: $this->errors['catalog'] + ) ?? null; } // Search among products in the $category @@ -307,9 +316,10 @@ final class catalog extends core filter: $_filters, sort: $_sort, amount: 50, // @todo pagination + return: 'DISTINCT MERGE(d, {name: d.name.@language, description: d.description.@language, compatibility: d.compatibility.@language, brand: d.brand.@language, cost: d.cost.@currency})', language: $this->language, + currency: $this->currency, parameters: $_parameters, - return: 'DISTINCT MERGE(d, {name: d.name.@language, description: d.description.@language, compatibility: d.compatibility.@language})', errors: $this->errors['catalog'] ); } diff --git a/mirzaev/arming_bot/system/controllers/core.php b/mirzaev/arming_bot/system/controllers/core.php index 22e59a2..0b91235 100755 --- a/mirzaev/arming_bot/system/controllers/core.php +++ b/mirzaev/arming_bot/system/controllers/core.php @@ -10,8 +10,10 @@ use mirzaev\arming_bot\views\templater, mirzaev\arming_bot\models\account, mirzaev\arming_bot\models\session, mirzaev\arming_bot\models\settings, + mirzaev\arming_bot\models\cart, mirzaev\arming_bot\models\suspension, - mirzaev\arming_bot\models\enumerations\language; + mirzaev\arming_bot\models\enumerations\language, + mirzaev\arming_bot\models\enumerations\currency; // Framework for PHP use mirzaev\minimal\controller; @@ -37,20 +39,30 @@ class core extends controller protected readonly settings $settings; /** - * Instance of a session + * Instance of the session */ protected readonly session $session; /** - * Instance of an account + * Instance of the account */ protected readonly ?account $account; + /** + * Instance of the cart + */ + protected readonly ?cart $cart; + /** * Language */ protected language $language = language::en; + /** + * Currency + */ + protected currency $currency = currency::usd; + /** * Registry of errors */ @@ -66,7 +78,9 @@ class core extends controller * * @return void * - * @todo settings account и session не имеют проверок на возврат null + * @todo + * 1. settings account и session не имеют проверок на возврат null + * 2. TRANSIT EVERYTHING TO MIDDLEWARES */ public function __construct(bool $initialize = true) { @@ -118,15 +132,21 @@ class core extends controller // Initializing of the settings $this->settings = settings::active(); - // Initializing of language - if ($this->account?->language) $this->language = $this->account->language ?? language::en; - else if ($this->settings?->language) $this->language = $this->settings->language ?? language::en; + // Initializing of the language + $this->language = $this->account?->language ?? $this->session?->buffer['language'] ?? $this->settings?->language ?? language::en; + + // Initializing of the currency + $this->currency = $this->account?->currency ?? $this->session?->buffer['currency'] ?? $this->settings?->currency ?? currency::usd; + + // Initializing of the cart + $this->cart = $this->account?->cart() ?? $this->session?->cart(); // Initializing of preprocessor of views $this->view = new templater( session: $this->session, account: $this->account, - settings: $this->settings + settings: $this->settings, + cart: $this->cart ); // @todo перенести в middleware diff --git a/mirzaev/arming_bot/system/models/account.php b/mirzaev/arming_bot/system/models/account.php index 4077f7a..50f4ad8 100755 --- a/mirzaev/arming_bot/system/models/account.php +++ b/mirzaev/arming_bot/system/models/account.php @@ -8,10 +8,12 @@ namespace mirzaev\arming_bot\models; use mirzaev\arming_bot\models\core, mirzaev\arming_bot\models\traits\status, mirzaev\arming_bot\models\traits\buffer, + mirzaev\arming_bot\models\traits\cart, mirzaev\arming_bot\models\traits\document as document_trait, mirzaev\arming_bot\models\interfaces\document as document_interface, mirzaev\arming_bot\models\interfaces\collection as collection_interface, - mirzaev\arming_bot\models\enumerations\language; + mirzaev\arming_bot\models\enumerations\language, + mirzaev\arming_bot\models\enumerations\currency; // Framework for ArangoDB use mirzaev\arangodb\collection, @@ -36,8 +38,9 @@ use exception; */ final class account extends core implements document_interface, collection_interface { - use status, document_trait, buffer { + use status, document_trait, buffer, cart { buffer::write as write; + cart::initialize as cart; } /** @@ -85,6 +88,7 @@ final class account extends core implements document_interface, collection_inter // Abstractioning of parameters if (isset($result->language)) $result->language = language::{$result->language}; + if (isset($result->currency)) $result->currency = currency::{$result->currency}; // Writing the instance of account document from ArangoDB to the implement object $account->__document($result); diff --git a/mirzaev/arming_bot/system/models/cart.php b/mirzaev/arming_bot/system/models/cart.php index bd2193e..f908c38 100755 --- a/mirzaev/arming_bot/system/models/cart.php +++ b/mirzaev/arming_bot/system/models/cart.php @@ -6,13 +6,27 @@ namespace mirzaev\arming_bot\models; // Files of the project use mirzaev\arming_bot\models\core, + mirzaev\arming_bot\models\reservation, mirzaev\arming_bot\models\traits\document as document_trait, mirzaev\arming_bot\models\interfaces\document as document_interface, - mirzaev\arming_bot\models\interfaces\collection as collection_interface; + mirzaev\arming_bot\models\interfaces\collection as collection_interface, + mirzaev\arming_bot\models\enumerations\language, + mirzaev\arming_bot\models\enumerations\currency; + +// Framework for ArangoDB +use mirzaev\arangodb\collection, + mirzaev\arangodb\document; + +// Library for ArangoDB +use ArangoDBClient\Document as _document; + +// Built-in libraries +use exception; /** * Model of cart * + * @uses reservation * @package mirzaev\arming_bot\models * * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License @@ -26,4 +40,240 @@ final class cart extends core implements document_interface, collection_interfac * Name of the collection in ArangoDB */ final public const string COLLECTION = 'cart'; + + /** + * Search for all products + * + * Search for all products in the cart + * + * @param language|null $language Language + * @param currency|null $currency Currency + * @param array &$errors Registry of errors + * + * @return array|null Array with implementing objects of documents from ArangoDB, if found + */ + public function all( + ?language $language = language::en, + ?currency $currency = currency::usd, + array &$errors = [] + ): ?array { + try { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) { + if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) { + // Initialized collections + + // Search for all products in the cart + $result = @collection::execute( + << 'catalog', + 'cart' => $this->getId(), + 'collection' => product::COLLECTION, + 'language' => $language->name, + 'currency' => $currency->name + ], + flat: true, + errors: $errors + ); + + /* + * МеНЯ ЭТО РАЗДРАЖАЕТ + */ + + $products = []; + + foreach ($result ?? [] as $raw) { + foreach ($raw as $key => $value) { + $products[$key] = $value; + } + } + + // Exit (success) + return $products; + } else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION); + } else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Count + * + * Count of the product in the cart + * + * @param product $product The product + * @param int $limit Limit for counting + * @param array &$errors Registry of errors + * + * @return int|null Amount of the product in the cart, if counted + */ + public function count(product $product, int $limit = 100, array &$errors = []): ?int + { + try { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) { + if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) { + // Initialized collections + + // Search for the products in the cart and count them + return (int) collection::execute( + << 'catalog', + 'cart' => $this->getId(), + 'collection' => product::COLLECTION, + 'product' => $product->getId(), + 'limit' => $limit + ], + errors: $errors + ); + } else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION); + } else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /* + * Write + * + * Write the product in the cart + * + * @param product $product The product + * @param int $amount Amount of writings + * @param array &$errors Registry of errors + * + * @return void + */ + public function write(product $product, int $amount = 1, array &$errors = []): void + { + try { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) { + if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) { + // Initialized collections + + // Writing the product to the cart + collection::execute( + << $this->getId(), + 'product' => $product->getId(), + '@edge' => reservation::COLLECTION, + 'amount' => $amount, + 'created' => time() + ], + errors: $errors + ); + } else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION); + } else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } + + /* + * Delete + * + * Delete the product from the cart + * + * @param product $product The product + * @param int $amount Amount of deletings + * @param array &$errors Registry of errors + * + * @return void + */ + public function delete(product $product, int $amount = 1, array &$errors = []): void + { + try { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + if (collection::initialize(reservation::COLLECTION, reservation::TYPE, errors: $errors)) { + if (collection::initialize(product::COLLECTION, product::TYPE, errors: $errors)) { + // Initialized collections + + // Deleting the product from the cart + collection::execute( + << 'catalog', + 'cart' => $this->getId(), + 'collection' => product::COLLECTION, + 'product' => $product->getId(), + '@reservation' => reservation::COLLECTION, + 'amount' => $amount + ], + errors: $errors + ); + } else throw new exception('Failed to initialize ' . product::TYPE . ' collection: ' . product::COLLECTION); + } else throw new exception('Failed to initialize ' . reservation::TYPE . ' collection: ' . reservation::COLLECTION); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } } diff --git a/mirzaev/arming_bot/system/models/catalog.php b/mirzaev/arming_bot/system/models/catalog.php index dd96e54..23df79b 100755 --- a/mirzaev/arming_bot/system/models/catalog.php +++ b/mirzaev/arming_bot/system/models/catalog.php @@ -11,6 +11,7 @@ use mirzaev\arming_bot\models\core, mirzaev\arming_bot\models\entry, mirzaev\arming_bot\models\traits\files, mirzaev\arming_bot\models\enumerations\language, + mirzaev\arming_bot\models\enumerations\currency, mirzaev\arming_bot\models\traits\yandex\disk as yandex; // Framework for ArangoDB @@ -77,6 +78,7 @@ final class catalog extends core int &$products_old = 0, int &$products_new = 0, language $language = language::en, + currency $currency = currency::usd, array &$errors = [] ): void { try { @@ -314,7 +316,7 @@ final class catalog extends core (int) $row['identifier'], [$language->name => $row['name']], [$language->name => $row['description']], - (float) $row['cost'], + [$currency->name => (float) $row['cost']], (float) $row['weight'], ['x' => $row['x'], 'y' => $row['y'], 'z' => $row['z']], [$language->name => $row['brand']], diff --git a/mirzaev/arming_bot/system/models/core.php b/mirzaev/arming_bot/system/models/core.php index e54006e..528139c 100755 --- a/mirzaev/arming_bot/system/models/core.php +++ b/mirzaev/arming_bot/system/models/core.php @@ -113,8 +113,8 @@ class core extends model errors: $errors ); - if ($result instanceof _document) { - // Received only 1 document and + if ($amount === 1 && $result instanceof _document) { + // Received only 1 document and @todo rebuild // Initializing the object $object = new static; diff --git a/mirzaev/arming_bot/system/models/entry.php b/mirzaev/arming_bot/system/models/entry.php index b4ee141..882fd5f 100755 --- a/mirzaev/arming_bot/system/models/entry.php +++ b/mirzaev/arming_bot/system/models/entry.php @@ -91,38 +91,47 @@ final class entry extends core implements document_interface, collection_interfa } /** - * Find ascendants + * Ascendants * - * Find ascendants that are not descendants for anyone + * Search for ascendants that are not descendants for anyone * * @param category|product $descendant Descendant document + * @param string|null $return Return (AQL) + * @param array $parameters Binded parameters for placeholders ['placeholder' => parameter] * @param array &$errors Registry of errors * * @return array|null Ascendants that are not descendants for anyone, if found */ public static function ascendants( category|product $descendant, + ?string $return = 'DISTINCT ascendant', + array $parameters = [], array &$errors = [] ): ?array { try { if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { // Initialized the collection - if ($ascendants = collection::execute( - <<<'AQL' - FOR d IN @@collection - FOR ascendant IN OUTBOUND d @@edge - RETURN DISTINCT ascendant - AQL, + + // Search for ascendants + if ($result = collection::execute( + sprintf( + <<<'AQL' + FOR d IN @@collection + FOR ascendant IN OUTBOUND d @@edge + RETURN %s + AQL, + empty($return) ? 'DISTINCT ascendant' : $return + ), [ '@collection' => $descendant::COLLECTION, '@edge' => static::COLLECTION - ], + ] + $parameters, errors: $errors )) { // Found ascendants // Exit (success) - return is_array($ascendants) ? $ascendants : [$ascendants]; + return is_array($result) ? $result : [$result]; } else return []; } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { @@ -211,6 +220,9 @@ final class entry extends core implements document_interface, collection_interfa * @param string|null $sort Expression for sorting (AQL) * @param int $page Страница * @param int $amount Количество товаров на странице + * @param string|null $categories_merge Expression with paremeters to return for categories (AQL) + * @param string|null $products_merge Expression with paremeters to return for products (AQL) + * @param array $parameters Binded parameters for placeholders ['placeholder' => parameter] * @param array &$errors Registry of errors * * @return array Массив с найденными вхождениями (может быть пустым) @@ -221,6 +233,9 @@ final class entry extends core implements document_interface, collection_interfa ?string $sort = 'v.position ASC, v.created DESC', int $page = 1, int $amount = 100, + ?string $categories_merge = null, + ?string $products_merge = null, + array $parameters = [], array &$errors = [] ): array { try { @@ -236,18 +251,21 @@ final class entry extends core implements document_interface, collection_interfa %s %s LIMIT @offset, @amount - LET _type = (FOR v2 IN INBOUND v._id GRAPH @graph RETURN v2)[0] ? "category" : "product" - RETURN MERGE(v, {_type}) + RETURN DISTINCT IS_SAME_COLLECTION(@category, v._id) ? MERGE(v, {_type: @category%s}) : MERGE(v, {_type: @product%s}) AQL, empty($filter) ? '' : "FILTER $filter", empty($sort) ? '' : "SORT $sort", + empty($categories_merge) ? '' : ", $categories_merge", + empty($products_merge) ? '' : ", $products_merge" ), [ + 'category' => category::COLLECTION, + 'product' => product::COLLECTION, 'graph' => 'catalog', 'document' => $document->getId(), 'offset' => --$page <= 0 ? $page = 0 : $page * $amount, 'amount' => $amount - ], + ] + $parameters, errors: $errors )) ? $result : [$result]; } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); diff --git a/mirzaev/arming_bot/system/models/enumerations/currency.php b/mirzaev/arming_bot/system/models/enumerations/currency.php new file mode 100644 index 0000000..911541b --- /dev/null +++ b/mirzaev/arming_bot/system/models/enumerations/currency.php @@ -0,0 +1,69 @@ + + */ +enum currency +{ + case usd; + case rub; + + /** + * Label + * + * Initialize label of the currency + * + * @param language|null $language Language into which to translate + * + * @return string Translated label of the currency + * + * @todo + * 1. More currencies + * 2. Cases??? + */ + public function label(?language $language = language::en): string + { + // Exit (success) + return match ($this) { + currency::usd => match ($language) { + language::en => 'Dollar', + language::ru => 'Доллар' + }, + currency::rub => match ($language) { + language::en => 'Ruble', + language::ru => 'Рубль' + } + }; + } + + /** + * Symbol + * + * Initialize symbol of the currency + * + * @return string Symbol of the currency + * + * @todo + * 1. More currencies + */ + public function symbol(): string + { + // Exit (success) + return match ($this) { + currency::usd => '$', + currency::rub => '₽' + }; + } +} diff --git a/mirzaev/arming_bot/system/models/enumerations/language.php b/mirzaev/arming_bot/system/models/enumerations/language.php index bae35ec..1f7595d 100644 --- a/mirzaev/arming_bot/system/models/enumerations/language.php +++ b/mirzaev/arming_bot/system/models/enumerations/language.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace mirzaev\arming_bot\models\enumerations; /** - * Types of human languages + * Types of languages by ISO 639-1 standart * * @package mirzaev\arming_bot\models\enumerations * @@ -18,17 +18,19 @@ enum language case ru; /** - * Translate label of language + * Label + * + * Initialize label of the language * * @param language|null $language Language into which to translate * - * @return string Translated label of language + * @return string Translated label of the language * * @todo * 1. More languages * 2. Cases??? */ - public function translate(?language $language = language::en): string + public function label(?language $language = language::en): string { // Exit (success) return match ($this) { diff --git a/mirzaev/arming_bot/system/models/product.php b/mirzaev/arming_bot/system/models/product.php index 46b39a8..abae9ed 100755 --- a/mirzaev/arming_bot/system/models/product.php +++ b/mirzaev/arming_bot/system/models/product.php @@ -6,10 +6,11 @@ namespace mirzaev\arming_bot\models; // Files of the project use mirzaev\arming_bot\models\core, - mirzaev\arming_bot\models\enumerations\language, mirzaev\arming_bot\models\traits\document as document_trait, mirzaev\arming_bot\models\interfaces\document as document_interface, - mirzaev\arming_bot\models\interfaces\collection as collection_interface; + mirzaev\arming_bot\models\interfaces\collection as collection_interface, + mirzaev\arming_bot\models\enumerations\language, + mirzaev\arming_bot\models\enumerations\currency; // Framework for ArangoDB use mirzaev\arangodb\collection, @@ -63,7 +64,7 @@ final class product extends core implements document_interface, collection_inter int $identifier, array $name = [['en' => 'ERROR']], ?array $description = [['en' => 'ERROR']], - float $cost = 0, + array $cost = [['usd' => 0]], float $weight = 0, array $dimensions = ['x' => 0, 'y' => 0, 'z' => 0], ?array $brand = [['en' => 'ERROR']], @@ -123,11 +124,14 @@ final class product extends core implements document_interface, collection_inter * @param int $page Page * @param int $amount Amount per page * @param string|null $return Return (AQL) - * @param language|null $language Language code (en, ru...) + * @param language|null $language Language + * @param currency|null $currency Currency * @param array $parameters Binded parameters for placeholders ['placeholder' => parameter] * @param array &$errors Registry of errors * * @return array|static Found products or instance of the product from ArangoDB (can be empty) + * + * @todo убрать language и currency */ public static function read( ?string $search = null, @@ -137,6 +141,7 @@ final class product extends core implements document_interface, collection_inter int $amount = 100, ?string $return = 'DISTINCT d', ?language $language = null, + ?currency $currency = null, array $parameters = [], array &$errors = [] ): array|static { @@ -147,6 +152,9 @@ final class product extends core implements document_interface, collection_inter // Initializing of the language parameter if ($language instanceof language) $parameters['language'] = $language->name; + // Initializing of the currency parameter + if ($currency instanceof currency) $parameters['currency'] = $currency->name; + // Initializing parameters for search if ($search) $parameters += [ 'search' => $search, @@ -158,10 +166,10 @@ final class product extends core implements document_interface, collection_inter sprintf( <<<'AQL' FOR d IN @@collection %s - %s - %s - LIMIT @offset, @amount - RETURN %s + %s + %s + LIMIT @offset, @amount + RETURN %s AQL, empty($search) ? '' : <<<'AQL' SEARCH @@ -196,8 +204,8 @@ final class product extends core implements document_interface, collection_inter errors: $errors ); - if ($result instanceof _document) { - // Found product + if ($amount === 1 && $result instanceof _document) { + // Found product @todo need to rebuild this // Initializing the object $product = new static; @@ -214,7 +222,7 @@ final class product extends core implements document_interface, collection_inter } // Exit (success) - return $result ?? []; + return is_array($result) ? $result : [$result]; } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); } catch (exception $e) { // Writing to the registry of errors diff --git a/mirzaev/arming_bot/system/models/reservation.php b/mirzaev/arming_bot/system/models/reservation.php new file mode 100755 index 0000000..2f58912 --- /dev/null +++ b/mirzaev/arming_bot/system/models/reservation.php @@ -0,0 +1,39 @@ + + */ +final class reservation extends core implements document_interface, collection_interface +{ + use document_trait; + + /** + * Name of the collection in ArangoDB + */ + final public const string COLLECTION = 'reservation'; + + /** + * Type of the collection in ArangoDB + */ + public const type TYPE = type::edge; +} diff --git a/mirzaev/arming_bot/system/models/session.php b/mirzaev/arming_bot/system/models/session.php index b86aeae..f19b1ff 100755 --- a/mirzaev/arming_bot/system/models/session.php +++ b/mirzaev/arming_bot/system/models/session.php @@ -7,20 +7,21 @@ namespace mirzaev\arming_bot\models; // Files of the project use mirzaev\arming_bot\models\account, mirzaev\arming_bot\models\connect, - mirzaev\arming_bot\models\cart, mirzaev\arming_bot\models\enumerations\session as verification, mirzaev\arming_bot\models\traits\status, mirzaev\arming_bot\models\traits\buffer, + mirzaev\arming_bot\models\traits\cart, mirzaev\arming_bot\models\traits\document as document_trait, mirzaev\arming_bot\models\interfaces\document as document_interface, mirzaev\arming_bot\models\interfaces\collection as collection_interface, - mirzaev\arming_bot\models\enumerations\language; + mirzaev\arming_bot\models\enumerations\language, + mirzaev\arming_bot\models\enumerations\currency; // Framework for ArangoDB use mirzaev\arangodb\collection, mirzaev\arangodb\document; -// Library для ArangoDB +// Library for ArangoDB use ArangoDBClient\Document as _document; // Built-in libraries @@ -36,8 +37,9 @@ use exception; */ final class session extends core implements document_interface, collection_interface { - use status, document_trait, buffer { + use status, document_trait, buffer, cart { buffer::write as write; + cart::initialize as cart; } /** @@ -149,13 +151,15 @@ final class session extends core implements document_interface, collection_inter // Search for connected account $result = collection::execute( << 'users', + 'collection' => account::COLLECTION, 'session' => $this->getId() ], errors: $errors @@ -172,6 +176,7 @@ final class session extends core implements document_interface, collection_inter // Abstractioning of parameters if (isset($result->language)) $result->language = language::{$result->language}; + if (isset($result->currency)) $result->currency = currency::{$result->currency}; // Writing the instance of account document from ArangoDB to the implement object $account->__document($result); @@ -197,114 +202,6 @@ final class session extends core implements document_interface, collection_inter return null; } - /** - * Search for a connected cart - * - * @param array &$errors Registry of errors - * - * @return cart|null An object implements the instance of the cart document from ArangoDB, if found - */ - public function cart(array &$errors = []): ?cart - { - try { - if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { - if (collection::initialize(connect::COLLECTION, connect::TYPE, errors: $errors)) { - if (collection::initialize(cart::COLLECTION, cart::TYPE, errors: $errors)) { - // Initialized collections - - // Search for connected cart - $result = collection::execute( - << $this->getId() - ], - errors: $errors - ); - - if ($result instanceof _document) { - // Found the cart - - // Initializing the object - $cart = new cart; - - if (method_exists($cart, '__document')) { - // Object can implement a document from ArangoDB - - // Writing the instance of cart document from ArangoDB to the implement object - $cart->__document($result); - - // Exit (success) - return $cart; - } - } else { - // Not found the cart - - // Initializing a new cart and write they into ArangoDB - $_id = document::write( - cart::COLLECTION, - [ - 'active' => true, - ] - ); - - if ($result = collection::execute( - <<<'AQL' - FOR d IN @@collection - FILTER d._id == @_id && d.active == true - RETURN d - AQL, - [ - '@collection' => cart::COLLECTION, - '_id' => $_id - ], - errors: $errors - )) { - // Found the instance of just created new cart - - // Initializing the object - $cart = new cart; - - if (method_exists($cart, '__document')) { - // Object can implement a document from ArangoDB - - // Writing the instance of cart document from ArangoDB to the implement object - $cart->__document($result); - - // Connecting the cart to the session - $connected = $this->connect($cart, $errors); - - if ($connected) { - // The cart has been connected to the session - - // Exit (success) - return $cart; - } else throw new exception('Failed to connect the cart to the session'); - } else throw new exception('Class ' . cart::class . ' does not implement a document from ArangoDB'); - } else throw new exception('Failed to create or find just created session'); - } - } else throw new exception('Failed to initialize ' . cart::TYPE . ' collection: ' . cart::COLLECTION); - } else throw new exception('Failed to initialize ' . connect::TYPE . ' collection: ' . connect::COLLECTION); - } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); - } catch (exception $e) { - // Writing to the registry of errors - $errors[] = [ - 'text' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'stack' => $e->getTrace() - ]; - } - - // Exit (fail) - return null; - } - /** * Search by hash * diff --git a/mirzaev/arming_bot/system/models/settings.php b/mirzaev/arming_bot/system/models/settings.php index 8f7a58e..a411e11 100755 --- a/mirzaev/arming_bot/system/models/settings.php +++ b/mirzaev/arming_bot/system/models/settings.php @@ -9,7 +9,8 @@ use mirzaev\arming_bot\models\core, mirzaev\arming_bot\models\traits\document as document_trait, mirzaev\arming_bot\models\interfaces\document as document_interface, mirzaev\arming_bot\models\interfaces\collection as collection_interface, - mirzaev\arming_bot\models\enumerations\language; + mirzaev\arming_bot\models\enumerations\language, + mirzaev\arming_bot\models\enumerations\currency; // Framework for ArangoDB use mirzaev\arangodb\collection, @@ -78,6 +79,7 @@ final class settings extends core implements document_interface, collection_inte // Abstractioning of parameters if (isset($result->language)) $result->language = language::{$result->language}; + if (isset($result->currency)) $result->currency = currency::{$result->currency}; // Writing the instance of settings document from ArangoDB to the implement object $settings->__document($result); diff --git a/mirzaev/arming_bot/system/models/traits/buffer.php b/mirzaev/arming_bot/system/models/traits/buffer.php index c3d8976..eef2096 100755 --- a/mirzaev/arming_bot/system/models/traits/buffer.php +++ b/mirzaev/arming_bot/system/models/traits/buffer.php @@ -18,13 +18,14 @@ use mirzaev\arangodb\collection, use exception; /** - * Trait for buffer + * Buffer * - * @uses collection_interface + * Storage of data in the document from ArangoDB * * @param static COLLECTION Name of the collection in ArangoDB * @param static TYPE Type of the collection in ArangoDB * + * @uses collection_interface * @package mirzaev\arming_bot\models\traits * * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License @@ -33,12 +34,12 @@ use exception; trait buffer { /** - * Write to buffer of the session + * Write to buffer of the document * * @param array $data Data for writing (merge) * @param array &$errors Registry of errors * - * @return bool Is data has written into the session document from ArangoDB? + * @return bool Is data has written into the document from ArangoDB? */ public function write(array $data, array &$errors = []): bool { @@ -46,10 +47,10 @@ trait buffer if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { // Initialized the collection - // The instance of the session document from ArangoDB is initialized? + // The instance of the document from ArangoDB is initialized? isset($this->document) || throw new exception('The instance of the sessoin document from ArangoDB is not initialized'); - // Writing data into buffer of the instance of the session document from ArangoDB + // Writing data into buffer of the instance of the document from ArangoDB $this->document->buffer = array_replace_recursive($this->document->buffer ?? [], $data); // Writing to ArangoDB and exit (success) diff --git a/mirzaev/arming_bot/system/models/traits/cart.php b/mirzaev/arming_bot/system/models/traits/cart.php new file mode 100755 index 0000000..29b103c --- /dev/null +++ b/mirzaev/arming_bot/system/models/traits/cart.php @@ -0,0 +1,151 @@ + + */ +trait cart +{ + use document_trait; + + /** + * Cart + * + * Search for a connected cart + * + * @param array &$errors Registry of errors + * + * @return model|null An object implements the instance of the cart document from ArangoDB, if found + */ + public function initialize(array &$errors = []): ?model + { + try { + if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) { + if (collection::initialize(connect::COLLECTION, connect::TYPE, errors: $errors)) { + if (collection::initialize(model::COLLECTION, model::TYPE, errors: $errors)) { + // Initialized collections + + // Search for connected cart + $result = collection::execute( + << model::COLLECTION, + 'graph' => 'users', + 'document' => $this->document->getId() + ], + errors: $errors + ); + + if ($result instanceof _document) { + // Found the cart + + // Initializing the object + $cart = new model; + + if (method_exists($cart, '__document')) { + // Object can implement a document from ArangoDB + + // Writing the instance of cart document from ArangoDB to the implement object + $cart->__document($result); + + // Exit (success) + return $cart; + } + } else { + // Not found the cart + + // Initializing a new cart and write they into ArangoDB + $_id = document::write( + model::COLLECTION, + [ + 'active' => true, + ] + ); + + if ($result = collection::execute( + <<<'AQL' + FOR d IN @@collection + FILTER d._id == @_id && d.active == true + RETURN d + AQL, + [ + '@collection' => model::COLLECTION, + '_id' => $_id + ], + errors: $errors + )) { + // Found the instance of just created new cart + + // Initializing the object + $cart = new model; + + if (method_exists($cart, '__document')) { + // Object can implement a document from ArangoDB + + // Writing the instance of cart document from ArangoDB to the implement object + $cart->__document($result); + + // Connecting the cart to the document + $connected = $this->connect($cart, $errors); + + if ($connected) { + // The cart has been connected to the document + + // Exit (success) + return $cart; + } else throw new exception('Failed to connect the ' . model::class . ' to the ' . static::class); + } else throw new exception('Class ' . model::class . ' does not implement a document from ArangoDB'); + } else throw new exception('Failed to create or find just created ' . static::class); + } + } else throw new exception('Failed to initialize ' . model::TYPE . ' collection: ' . model::COLLECTION); + } else throw new exception('Failed to initialize ' . connect::TYPE . ' collection: ' . connect::COLLECTION); + } else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION); + } catch (exception $e) { + // Writing to the registry of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } +} diff --git a/mirzaev/arming_bot/system/models/traits/document.php b/mirzaev/arming_bot/system/models/traits/document.php index aa6fa8f..daa1f72 100755 --- a/mirzaev/arming_bot/system/models/traits/document.php +++ b/mirzaev/arming_bot/system/models/traits/document.php @@ -23,10 +23,9 @@ use exception; /** * Trait for implementing a document instance from ArangoDB * - * @uses document_interface - * * @var protected readonly _document|null $document An instance of the ArangoDB document * + * @uses document_interface * @package mirzaev\arming_bot\models\traits * * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License diff --git a/mirzaev/arming_bot/system/public/index.php b/mirzaev/arming_bot/system/public/index.php index d3e9594..dc4563f 100755 --- a/mirzaev/arming_bot/system/public/index.php +++ b/mirzaev/arming_bot/system/public/index.php @@ -41,7 +41,8 @@ $router ->write('/', 'catalog', 'index', 'GET') ->write('/', 'catalog', 'index', 'POST') ->write('/cart', 'cart', 'index', 'GET') - ->write('/cart/write', 'cart', 'write', 'POST') + ->write('/cart', 'cart', 'index', 'POST') + ->write('/cart/product', 'cart', 'product', 'POST') ->write('/account/write', 'account', 'write', 'POST') ->write('/session/write', 'session', 'write', 'POST') ->write('/session/connect/telegram', 'session', 'telegram', 'POST') diff --git a/mirzaev/arming_bot/system/public/js/cart.js b/mirzaev/arming_bot/system/public/js/cart.js index 517fcd7..2701fa1 100755 --- a/mirzaev/arming_bot/system/public/js/cart.js +++ b/mirzaev/arming_bot/system/public/js/cart.js @@ -35,7 +35,9 @@ import("/js/core.js").then(() => */ core.cart = class cart { /** - * Write or delete product from the cart (interface) + * Toggle + * + * Toggle the product in the cart (interface) * * @param {HTMLElement} button Button of the product * @param {bool} force Ignore the damper? (false) @@ -54,7 +56,9 @@ import("/js/core.js").then(() => } /** - * Write or delete product from the cart (damper) + * Toggle + * + * Toggle the product in the cart (damper) * * @param {HTMLElement} button Button of the product * @param {bool} force Ignore the damper? (false) @@ -63,12 +67,14 @@ import("/js/core.js").then(() => */ static toggle_damper = core.damper( (...variables) => this.toggle_system(...variables), - 800, - 1, + 300, + 2, ); /** - * Write or delete product from the cart (system) + * Toggle + * + * Toggle the product in the cart (system) * * @param {HTMLElement} button Button of the product * @@ -80,15 +86,23 @@ import("/js/core.js").then(() => if (button instanceof HTMLElement) { // Validated + // Initializing of the wrap of buttons + const wrap = button.parentElement; + + // Initializing of the product + const product = wrap.parentElement; + // Initializing of identifier of the product - const identifier = button.getAttribute("data-product-identifier"); + const identifier = product.getAttribute( + "data-product-identifier", + ); if (typeof identifier === "string" && identifier.length > 0) { // Validated identifier return await core.request( - "/cart/write", - `product=${identifier}`, + "/cart/product", + `identifier=${identifier}&type=toggle`, ) .then((json) => { if ( @@ -103,16 +117,388 @@ import("/js/core.js").then(() => // Unblocking the button button.removeAttribute("disabled"); - if (json.status) { - // The product in the cart + // Writing offset of hue-rotate to indicate that the product is in the cart + wrap.style.setProperty( + "--hue-rotate-offset", + json.amount + '0deg', + ); - // Writing style of added to the cart button - button.classList.add("cart"); - } else { - // The product is not in the cart + // Writing attribute with amount of the product in the cart + product.setAttribute( + "data-product-amount", + json.amount, + ); - // Deleting style of added to the cart button - button.classList.remove("cart"); + // Initializing the amount element + const amount = wrap.querySelector( + 'span[data-product-button-text="amount"]', + ); + + if (amount instanceof HTMLElement) { + // Initialized the amount element + + // Writing amount of the product in the cart + amount.innerText = json.amount; + } + } + }); + } + } + } + + /** + * Write + * + * Write the product in the cart (interface) + * + * @param {HTMLElement} button Button of the product + * @param {number} amount Amount of writings + * @param {bool} force Ignore the damper? (false) + * + * @return {bool} True if an error occurs to continue the event execution + */ + static write(button, amount = 1, force = false) { + // Blocking the button + button.setAttribute("disabled", "true"); + + // Execute under damper + this.write_damper(button, amount, force); + + // Exit (success) + return false; + } + + /** + * Write + * + * Write the product in the cart (damper) + * + * @param {HTMLElement} button Button of the product + * @param {number} amount Amount of writings + * @param {bool} force Ignore the damper? (false) + * + * @return {void} + */ + static write_damper = core.damper( + (...variables) => this.write_system(...variables), + 300, + 3, + ); + + /** + * Write + * + * Write the product in the cart (system) + * + * @param {HTMLElement} button Button of the product + * @param {number} amount Amount of writings + * + * @return {Promise} Request to the server + * + * @todo add unblocking button by timer + everywhere + */ + static async write_system(button, amount = 1) { + if ( + button instanceof HTMLElement && + typeof amount === "number" && + amount > -1 && + amount < 100 + ) { + // Validated + + // Initializing of the wrap of buttons + const wrap = button.parentElement; + + // Initializing of the product + const product = wrap.parentElement; + + // Initializing of identifier of the product + const identifier = product.getAttribute( + "data-product-identifier", + ); + + if (typeof identifier === "string" && identifier.length > 0) { + // Validated identifier + + return await core.request( + "/cart/product", + `identifier=${identifier}&type=write&amount=${amount}`, + ) + .then((json) => { + if ( + json.errors !== null && + typeof json.errors === "object" && + json.errors.length > 0 + ) { + // Fail (received errors) + } else { + // Success (not received errors) + + // Unblocking the button + button.removeAttribute("disabled"); + + // Writing offset of hue-rotate to indicate that the product is in the cart + wrap.style.setProperty( + "--hue-rotate-offset", + json.amount + '0deg', + ); + + // Writing attribute with amount of the product in the cart + product.setAttribute( + "data-product-amount", + json.amount, + ); + + // Initializing the amount element + const amount = wrap.querySelector( + 'span[data-product-button-text="amount"]', + ); + + if (amount instanceof HTMLElement) { + // Initialized the amount element + + // Writing amount of the product in the cart + amount.innerText = json.amount; + } + } + }); + } + } + } + + /** + * Delete + * + * Delete the product from the cart (interface) + * + * @param {HTMLElement} button Button of the product + * @param {number} amount Amount of deletings + * @param {bool} force Ignore the damper? (false) + * + * @return {bool} True if an error occurs to continue the event execution + */ + static delete(button, amount = 1, force = false) { + // Blocking the button + button.setAttribute("disabled", "true"); + + // Execute under damper + this.delete_damper(button, amount, force); + + // Exit (success) + return false; + } + + /** + * Delete + * + * Delete the product from the cart (damper) + * + * @param {HTMLElement} button Button of the product + * @param {number} amount Amount of deletings + * @param {bool} force Ignore the damper? (false) + * + * @return {void} + */ + static delete_damper = core.damper( + (...variables) => this.delete_system(...variables), + 300, + 3, + ); + + /** + * Delete + * + * Delete the product from the cart (system) + * + * @param {HTMLElement} button Button of the product + * @param {number} amount Amount of deletings + * + * @return {Promise} Request to the server + * + * @todo add unblocking button by timer + everywhere + */ + static async delete_system(button, amount = 1) { + if ( + button instanceof HTMLElement && + typeof amount === "number" && + amount > 0 && + amount < 101 + ) { + // Validated + + // Initializing of the wrap of buttons + const wrap = button.parentElement; + + // Initializing of the product + const product = wrap.parentElement; + + // Initializing of identifier of the product + const identifier = product.getAttribute( + "data-product-identifier", + ); + + if (typeof identifier === "string" && identifier.length > 0) { + // Validated identifier + + return await core.request( + "/cart/product", + `identifier=${identifier}&type=delete&amount=${amount}`, + ) + .then((json) => { + if ( + json.errors !== null && + typeof json.errors === "object" && + json.errors.length > 0 + ) { + // Fail (received errors) + } else { + // Success (not received errors) + + // Unblocking the button + button.removeAttribute("disabled"); + + // Writing offset of hue-rotate to indicate that the product is in the cart + wrap.style.setProperty( + "--hue-rotate-offset", + json.amount + '0deg', + ); + + // Writing attribute with amount of the product in the cart + product.setAttribute( + "data-product-amount", + json.amount, + ); + + // Initializing the amount element + const amount = wrap.querySelector( + 'span[data-product-button-text="amount"]', + ); + + if (amount instanceof HTMLElement) { + // Initialized the amount element + + // Writing amount of the product in the cart + amount.innerText = json.amount; + } + } + }); + } + } + } + + /** + * Set + * + * Set amount of the product in the cart (interface) + * + * @param {HTMLElement} button Button of the product + * @param {number} amount Amount of the product in the cart to be setted + * @param {bool} force Ignore the damper? (false) + * + * @return {bool} True if an error occurs to continue the event execution + */ + static set(button, amount = 1, force = false) { + // Blocking the button + button.setAttribute("disabled", "true"); + + // Execute under damper + this.set_damper(button, amount, force); + + // Exit (success) + return false; + } + + /** + * Set + * + * Set the product in the cart (damper) + * + * @param {HTMLElement} button Button of the product + * @param {number} amount Amount of the product in the cart to be setted + * @param {bool} force Ignore the damper? (false) + * + * @return {void} + */ + static set_damper = core.damper( + (...variables) => this.set_system(...variables), + 300, + 3, + ); + + /** + * Set + * + * Set the product in the cart (system) + * + * @param {HTMLElement} button Button of the product + * @param {number} amount Amount of the product in the cart to be setted + * + * @return {Promise} Request to the server + * + * @todo add unblocking button by timer + everywhere + */ + static async set_system(button, amount = 1) { + if ( + button instanceof HTMLElement && + typeof amount === "number" && + amount > -1 && + amount < 101 + ) { + // Validated + + // Initializing of the wrap of buttons + const wrap = button.parentElement; + + // Initializing of the product + const product = wrap.parentElement; + + // Initializing of identifier of the product + const identifier = product.getAttribute( + "data-product-identifier", + ); + + if (typeof identifier === "string" && identifier.length > 0) { + // Validated identifier + + return await core.request( + "/cart/product", + `identifier=${identifier}&type=set&amount=${amount}`, + ) + .then((json) => { + if ( + json.errors !== null && + typeof json.errors === "object" && + json.errors.length > 0 + ) { + // Fail (received errors) + } else { + // Success (not received errors) + + // Unblocking the button + button.removeAttribute("disabled"); + + // Writing offset of hue-rotate to indicate that the product is in the cart + wrap.style.setProperty( + "--hue-rotate-offset", + json.amount + '0deg', + ); + + // Writing attribute with amount of the product in the cart + product.setAttribute( + "data-product-amount", + json.amount, + ); + + // Initializing the amount element + const amount = wrap.querySelector( + 'span[data-product-button-text="amount"]', + ); + + if (amount instanceof HTMLElement) { + // Initialized the amount element + + // Writing amount of the product in the cart + amount.innerText = json.amount; } } }); diff --git a/mirzaev/arming_bot/system/public/themes/default/css/catalog.css b/mirzaev/arming_bot/system/public/themes/default/css/catalog.css index f01a48b..0101d40 100644 --- a/mirzaev/arming_bot/system/public/themes/default/css/catalog.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/catalog.css @@ -137,17 +137,44 @@ main>section#products>div.column>article.product>a>p.title { background-color: var(--tg-theme-secondary-bg-color); } -main>section#products>div.column>article.product>button.cart { - filter: hue-rotate(120deg); -} - -main>section#products>div.column>article.product>button:last-of-type { +main>section#products>div.column>article.product>div[data-product="buttons"]:last-of-type { z-index: 100; height: 33px; display: flex; justify-content: center; align-items: center; - cursor: pointer; - color: var(--tg-theme-button-text-color); - background-color: var(--tg-theme-button-color); +} + +main>section#products>div.column>article.product[data-product-amount]:not(:is([data-product-amount="0"], [data-product-amount="1"]))>div[data-product="buttons"]:last-of-type { + container-type: inline-size; + container-name: product-buttons; +} + +main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"] { + padding: 0; + flex-grow: 1; +} + +main>section#products>div.column>article.product:is([data-product-amount="0"], [data-product-amount="1"])>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-button-text="amount"], +main>section#products>div.column>article.product[data-product-amount="0"]>div[data-product="buttons"]>button:is([data-product-button="write"], [data-product-button="delete"]) { + display: none; +} + +main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-button-text="amount"]:after { + content: '*'; + margin: 0 0.2rem; +} + +main>section#products>div.column>article.product[data-product-amount]:not([data-product-amount="0"])>div[data-product="buttons"] { + filter: hue-rotate(calc(120deg + var(--hue-rotate-offset, 0deg))); +} + +@container product-buttons (max-width: 200px) { + main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span:is([data-product-button-text="cost"], [data-product-button-text="currency"]) { + display: none; + } + + main>section#products>div.column>article.product>div[data-product="buttons"]>button[data-product-button="toggle"]>span[data-product-button-text="amount"]:after { + content: unset; + } } diff --git a/mirzaev/arming_bot/system/public/themes/default/css/icons/minus.css b/mirzaev/arming_bot/system/public/themes/default/css/icons/minus.css new file mode 100755 index 0000000..9e07218 --- /dev/null +++ b/mirzaev/arming_bot/system/public/themes/default/css/icons/minus.css @@ -0,0 +1,12 @@ +@charset "UTF-8"; + +i.icon.minus { + box-sizing: border-box; + position: relative; + display: block; + width: 16px; + height: 2px; + background: currentColor; + border-radius: 10px; +} + diff --git a/mirzaev/arming_bot/system/public/themes/default/css/icons/plus.css b/mirzaev/arming_bot/system/public/themes/default/css/icons/plus.css new file mode 100755 index 0000000..88320aa --- /dev/null +++ b/mirzaev/arming_bot/system/public/themes/default/css/icons/plus.css @@ -0,0 +1,24 @@ +@charset "UTF-8"; + +i.icon.plus, +i.icon.plus::after { + display: block; + box-sizing: border-box; + background: currentColor; + border-radius: 10px; +} +i.icon.plus { + margin-top: -2px; + position: relative; + width: 16px; + height: 2px; +} +i.icon.plus::after { + content: ""; + position: absolute; + width: 2px; + height: 16px; + top: -7px; + left: 7px; +} + diff --git a/mirzaev/arming_bot/system/public/themes/default/css/main.css b/mirzaev/arming_bot/system/public/themes/default/css/main.css index 5f7eb97..7b0626d 100755 --- a/mirzaev/arming_bot/system/public/themes/default/css/main.css +++ b/mirzaev/arming_bot/system/public/themes/default/css/main.css @@ -129,7 +129,7 @@ search:has(input:disabled) { backdrop-filter: contrast(0.5); } -*[type="button"] { +button, *[type="button"] { cursor: pointer; } @@ -144,6 +144,8 @@ search:has(input:disabled) { button { height: 33px; + color: var(--tg-theme-button-text-color); + background-color: var(--tg-theme-button-color); } a[type="button"] { diff --git a/mirzaev/arming_bot/system/views/templater.php b/mirzaev/arming_bot/system/views/templater.php index f286916..df8138e 100755 --- a/mirzaev/arming_bot/system/views/templater.php +++ b/mirzaev/arming_bot/system/views/templater.php @@ -7,7 +7,10 @@ namespace mirzaev\arming_bot\views; // Files of the project use mirzaev\arming_bot\models\session, mirzaev\arming_bot\models\account, - mirzaev\arming_bot\models\settings; + mirzaev\arming_bot\models\settings, + mirzaev\arming_bot\models\cart, + mirzaev\arming_bot\models\enumerations\language, + mirzaev\arming_bot\models\enumerations\currency; // Framework for PHP use mirzaev\minimal\controller; @@ -46,28 +49,35 @@ final class templater extends controller implements ArrayAccess * * @param session|null $session The object implementing a session instance from ArangoDB * @param account|null $account The object implementing an account instance from ArangoDB - * @param settings|null $settings The object implementing an account instance from ArangoDB + * @param settings|null $settings The object implementing a settings instance from ArangoDB + * @param cart|null $cart The object implementing a cart instance from ArangoDB * * @return void */ public function __construct( ?session $session = null, ?account $account = null, - ?settings $settings = null + ?settings $settings = null, + ?cart $cart = null ) { - // Initializing of an instance of twig + // Initializing an instance of twig $this->twig = new twig(new FilesystemLoader(VIEWS)); - // Initializing of global variables + // Declaring buffers for initializinf global variables + $language = $currency = null; + + // Initializing global variables $this->twig->addGlobal('theme', 'default'); $this->twig->addGlobal('server', $_SERVER); $this->twig->addGlobal('cookies', $_COOKIE); $this->twig->addGlobal('settings', $settings); if (!empty($session?->status())) $this->twig->addGlobal('session', $session); if (!empty($account?->status())) $this->twig->addGlobal('account', $account); - $this->twig->addGlobal('language', $account?->language->name ?? $settings?->language->name ?? 'en'); + $this->twig->addGlobal('language', $language = $account?->language ?? $session?->buffer['language'] ?? $settings?->language ?? language::en); + $this->twig->addGlobal('currency', $currency = $account?->currency ?? $session?->buffer['currency'] ?? $settings?->currency ?? currency::usd); + $this->twig->addGlobal('cart', $cart->all(language: $language, currency: $currency)); - // Initialize function of dimensions formattinx + // Initialize function of dimensions formatting $this->twig->addFunction( new TwigFunction( 'format_dimensions', diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html index 635f304..8587705 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/categories.html @@ -6,9 +6,9 @@ onkeydown="event.keyCode === 13 && (core.catalog.parameters.set('category', {{ category.identifier }}), core.catalog.search(event))" tabindex="3"> {% if category.images %} - {{ category.name[language] }} + {{ category.name }} {% endif %} -

{{ category.name[language] }}

+

{{ category.name }}

{% endfor %} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html index d2be5bc..85da2da 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/filters.html @@ -4,11 +4,11 @@ {% set buffer_brand = account.buffer.catalog.filters.brand ?? session.buffer.catalog.filters.brand %} {% for brand in filters.brands %} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html index bf68fbf..e35d076 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/products.html @@ -1,19 +1,25 @@ {% macro card(product) %} -{% set title = product.name[language] ~ ' ' ~ product.brand[language] ~ format_dimensions(product.dimensions.x, +{% set title = product.name ~ ' ' ~ product.brand ~ format_dimensions(product.dimensions.x, product.dimensions.y, product.dimensions.z, ' ') ~ ' ' ~ product.weight ~ 'г' %} -
- - {{ product.name[language] }} -

+{% set amount = cart[product.getId()].amount ?? 0 %} +

{% endmacro %} {% if products is not empty %} diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html index 93160b0..29b004a 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/elements/search.html @@ -1,7 +1,7 @@ {% set buffer_search = account.buffer.catalog.search.text ?? session.buffer.catalog.search.text %} - diff --git a/mirzaev/arming_bot/system/views/themes/default/catalog/page.html b/mirzaev/arming_bot/system/views/themes/default/catalog/page.html index 752b5c7..d393f6f 100755 --- a/mirzaev/arming_bot/system/views/themes/default/catalog/page.html +++ b/mirzaev/arming_bot/system/views/themes/default/catalog/page.html @@ -7,6 +7,8 @@ + + {% endblock %} {% block main %}