From 525d71dfe408a5536880491acca5732785f79f25 Mon Sep 17 00:00:00 2001 From: Arsen Mirzaev Tatyano-Muradovich Date: Wed, 25 Oct 2023 16:02:59 +0700 Subject: [PATCH] =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86?= =?UTF-8?q?=D1=8B=20=D1=81=D0=BF=D0=B8=D1=81=D0=BE=D0=BA=20=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=B2=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=20=D0=B4=D0=BE=D1=85=D0=B5?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B2=D1=81=D0=B5=D0=B3=D0=BE=20=D0=B2=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/controllers/administrator.php | 153 +++ mirzaev/ebala/system/controllers/operator.php | 28 +- mirzaev/ebala/system/controllers/task.php | 231 +++- mirzaev/ebala/system/models/core.php | 2 +- mirzaev/ebala/system/models/registry.php | 118 +- mirzaev/ebala/system/models/task.php | 2 +- .../ebala/system/public/css/animations.css | 14 + mirzaev/ebala/system/public/css/list.css | 54 +- .../public/css/pages/administrators.css | 63 + .../ebala/system/public/css/pages/markets.css | 4 +- .../system/public/css/pages/operators.css | 55 +- .../ebala/system/public/css/pages/tasks.css | 37 +- mirzaev/ebala/system/public/index.php | 8 +- .../ebala/system/public/js/administrators.js | 1082 +++++++++++++++++ mirzaev/ebala/system/public/js/markets.js | 2 +- mirzaev/ebala/system/public/js/operators.js | 1082 +++++++++++++++++ mirzaev/ebala/system/public/js/tasks.js | 370 +++++- .../system/views/elements/administrators.html | 21 + .../ebala/system/views/elements/markets.html | 6 +- .../system/views/elements/operators.html | 35 +- .../ebala/system/views/elements/tasks.html | 7 +- .../ebala/system/views/elements/workers.html | 2 +- .../system/views/pages/administrators.html | 92 ++ mirzaev/ebala/system/views/pages/markets.html | 1 + .../ebala/system/views/pages/operators.html | 4 +- mirzaev/ebala/system/views/pages/tasks.html | 4 +- 26 files changed, 3271 insertions(+), 206 deletions(-) create mode 100755 mirzaev/ebala/system/controllers/administrator.php create mode 100755 mirzaev/ebala/system/public/css/pages/administrators.css create mode 100644 mirzaev/ebala/system/public/js/administrators.js create mode 100644 mirzaev/ebala/system/public/js/operators.js create mode 100644 mirzaev/ebala/system/views/elements/administrators.html create mode 100644 mirzaev/ebala/system/views/pages/administrators.html diff --git a/mirzaev/ebala/system/controllers/administrator.php b/mirzaev/ebala/system/controllers/administrator.php new file mode 100755 index 0000000..4ea2ecb --- /dev/null +++ b/mirzaev/ebala/system/controllers/administrator.php @@ -0,0 +1,153 @@ + + */ +final class administrator extends core +{ + use errors; + + /** + * Главная страница + * + * @param array $parameters Параметры запроса + */ + public function index(array $parameters = []): ?string + { + // Авторизация + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'administrator')) { + // Авторизован аккаунт оператора или администратора + + foreach (['confirmed', 'waiting', 'published', 'unpublished', 'problematic', 'hided', 'completed'] as $name) { + // Перебор фильтров статусов + + // Инициализация значения (приоритет у cookie) + $value = $_COOKIE["administrators_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['administrators']['filters'][$name] ?? 0; + + // Инициализировано значение? + if ($value === null || $value === 0) continue; + + // Генерация класса для HTML-элемента по его состоянию (0 - ОТСУТСТВУЕТ, 1 - И, 2 - ИЛИ) + $this->view->{$name} = match ($value) { + '0', 0 => 'earth', + '1', 1 => 'sand', + '2', 2 => 'river', + default => 'earth' + }; + } + + // Генерация представлениямя + $main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'administrators.html'); + } else $main = $this->authorization(); + + // Возврат (успех) + if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', ['main' => $main]); + else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $main; + + // Возврат (провал) + return null; + } + + /** + * Прочитать + * + * @param array $parameters Параметры запроса + */ + public function read(array $parameters = []): ?string + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'administrator')) { + // Авторизован аккаунт оператора или администратора + + // Реинициализация актуальной страницы + if (isset($parameters['page'])) $this->session->write(['administrators' => ['page' => $parameters['page']]]); + else if (empty($this->session->buffer[$_SERVER['INTERFACE']]['administrators']['page'])) $this->session->write(['administrators' => ['page' => 1]]); + + // Инициализация буфера AQL-выражения для инъекции фильтра по интервалу + $polysemantic = ''; + + // Инициализация допустимых статусов + $statuses = ['active', 'inactive', 'fined', 'decent', 'hided', 'fired']; + + // Инициализация буфера AQL-выражения для инъекции фильтра по статусам (И) + $statuses_and = ''; + + foreach ($statuses as $name) { + // Перебор фильтров статусов (И) + + // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) + if (empty($value = $_COOKIE["administrators_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['administrators']['filters'][$name] ?? 0)) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($value === '1') $statuses_and .= " && administrator.$name == true"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $statuses_and = trim(trim(trim($statuses_and), '&&')); + + // Инициализация буфера AQL-выражения для инъекции фильтра по статусам (ИЛИ) + $statuses_or = ''; + + foreach ($statuses as $name) { + // Перебор фильтров статусов (ИЛИ) + + // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) + if (empty($value = $_COOKIE["administrators_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['administrators']['filters'][$name] ?? 0)) continue; + + // Генерация AQL-выражения для инъекции в строку запроса + if ($value === '2') $statuses_or .= " || administrator.$name == true"; + } + + // Очистка от бинарных операторов сравнения с только одним операндом (крайние) + $statuses_or = trim(trim(trim($statuses_or), '||')); + + // Инициализация буфера с объёдинёнными буферами c AQL-выражениям "И" и "ИЛИ" + $statuses_merged = (empty($statuses_and) ? '' : "($statuses_and)") . (empty($statuses_or) ? '' : (empty($statuses_and) ? '' : ' || ') . "($statuses_or)"); + + // Инициализация общего буфера с AQL-выражениями + $filters = ''; + + // Объединение фильров в единую строку с AQL-выражениями для инъекции + if (!empty($statuses_merged)) $filters .= empty($filters) ? $statuses_merged : " && ($statuses_merged)"; + + // Инициализация данных для генерации HTML-документа с таблицей + $this->view->rows = registry::administrators(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['administrators']['page']); + + // Запись в cookie (только таким методом можно записать "hostonly: true") + setcookie( + 'administrators_page', + (string) $this->session->buffer[$_SERVER['INTERFACE']]['administrators']['page'], + [ + 'expires' => strtotime('+1 hour'), + 'path' => '/', + 'secure' => true, + 'httponly' => false, + 'samesite' => 'strict' + ] + ); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы + $this->view->page = $parameters['page']; + + // Инициализация блока + return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'administrators.html'); + } + + // Возврат (провал) + return null; + } +} diff --git a/mirzaev/ebala/system/controllers/operator.php b/mirzaev/ebala/system/controllers/operator.php index e6838f4..8636f4a 100755 --- a/mirzaev/ebala/system/controllers/operator.php +++ b/mirzaev/ebala/system/controllers/operator.php @@ -7,7 +7,7 @@ namespace mirzaev\ebala\controllers; // Файлы проекта use mirzaev\ebala\controllers\core, mirzaev\ebala\controllers\traits\errors, - mirzaev\ebala\models\operator as model; + mirzaev\ebala\models\registry; // Библиотека для ArangoDB use ArangoDBClient\Document as _document; @@ -125,7 +125,7 @@ final class operator extends core if (!empty($statuses_merged)) $filters .= empty($filters) ? $statuses_merged : " && ($statuses_merged)"; // Инициализация данных для генерации HTML-документа с таблицей - $this->view->rows = model::list(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['operators']['page']); + $this->view->rows = registry::operators(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['operators']['page']); // Запись в cookie (только таким методом можно записать "hostonly: true") setcookie( @@ -150,28 +150,4 @@ final class operator extends core // Возврат (провал) return null; } - - /** - * Прочитать данные магазинов для - * - * @param array $parameters Параметры запроса - */ - public function datalist(array $parameters = []): ?string - { - if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'operator')) { - // Авторизован аккаунт оператора или магазина - - // Инициализация данных магазинов - $this->view->operators = model::read(filter: 'd.status == "active"', amount: 10000, return: '{ id: d.id, director: d.director }'); - - // Универсализация - if ($this->view->operators instanceof _document) $this->view->operators = [$this->view->operators]; - - // Возврат (успех) - return $this->view->render(DIRECTORY_SEPARATOR . 'lists' . DIRECTORY_SEPARATOR . 'operators.html'); - } - - // Возврат (провал) - return null; - } } diff --git a/mirzaev/ebala/system/controllers/task.php b/mirzaev/ebala/system/controllers/task.php index a46ee5a..bb422d4 100755 --- a/mirzaev/ebala/system/controllers/task.php +++ b/mirzaev/ebala/system/controllers/task.php @@ -85,10 +85,6 @@ final class task extends core if ($this->account->status()) { // Авторизован аккаунт - // Реинициализация актуальной страницы - if (isset($parameters['page'])) $this->session->write(['tasks' => ['page' => $parameters['page']]]); - else if (empty($this->session->buffer[$_SERVER['INTERFACE']]['tasks']['page'])) $this->session->write(['tasks' => ['page' => 1]]); - // Инициализация буфера AQL-выражения для инъекции фильтра по интервалу $interval = ''; @@ -125,12 +121,13 @@ final class task extends core // Очистка от бинарных операторов сравнения с только одним операндом (крайние) $polysemantic = trim(trim(trim($polysemantic), '&&')); + // Инициализация допустимых статусов - // @TODO Добавить инвертирование для waiting и unpublished $statuses = ['confirmed', 'waiting', 'published', 'unpublished', 'problematic', 'hided', 'completed']; - // Инициализация буфера AQL-выражения для инъекции фильтра по статусам (И) + // Инициализация буферов AQL-выражений для инъекции фильтра по статусам $statuses_and = ''; + $statuses_or = ''; foreach ($statuses as $name) { // Перебор фильтров статусов (И) @@ -138,27 +135,23 @@ final class task extends core // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) if (empty($value = $_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? 0)) continue; + // Конвертация ярлыков + $converted = match ($name) { + 'waiting' => 'confirmed', + 'unpublished' => 'published', + default => $name + }; + + // Генерация выражения + $expression = "task.$converted == " . ($name === 'unpublished' || $name === 'waiting' ? 'false' : 'true'); + // Генерация AQL-выражения для инъекции в строку запроса - if ($value === '1') $statuses_and .= " && task.$name == true"; + if ($value === '1') $statuses_and .= " && " . $expression; + else if ($value === '2') $statuses_or .= " || " . $expression; } // Очистка от бинарных операторов сравнения с только одним операндом (крайние) $statuses_and = trim(trim(trim($statuses_and), '&&')); - - // Инициализация буфера AQL-выражения для инъекции фильтра по статусам (ИЛИ) - $statuses_or = ''; - - foreach ($statuses as $name) { - // Перебор фильтров статусов (ИЛИ) - - // Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue) - if (empty($value = $_COOKIE["tasks_filter_$name"] ?? $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['filters'][$name] ?? 0)) continue; - - // Генерация AQL-выражения для инъекции в строку запроса - if ($value === '2') $statuses_or .= " || task.$name == true"; - } - - // Очистка от бинарных операторов сравнения с только одним операндом (крайние) $statuses_or = trim(trim(trim($statuses_or), '||')); // Инициализация буфера с объёдинёнными буферами c AQL-выражениям "И" и "ИЛИ" @@ -172,36 +165,60 @@ final class task extends core if (!empty($statuses_merged)) $filters .= empty($filters) ? $statuses_merged : " && ($statuses_merged)"; if (!empty($polysemantic)) $filters .= empty($filters) ? $polysemantic : " && $polysemantic"; - // Инициализация данных для генерации HTML-документа с таблицей - if ($_SERVER['INTERFACE'] === 'worker') - $this->view->rows = model::list(before: 'FILTER task.worker == "' . account::worker($this->account->number)?->id . '"' . empty($filters) ? null : " && ($filters)", page: (int) $this->session->buffer['worker']['tasks']['page']); - else if ($_SERVER['INTERFACE'] === 'operator') - $this->view->rows = model::list(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer['operator']['tasks']['page']); - else if ($_SERVER['INTERFACE'] === 'market') - $this->view->rows = model::list(before: 'FILTER task.market == "' . $this->market->id . '"' . empty($filters) ? null : " && ($filters)", page: (int) $this->session->buffer['market']['tasks']['page']); - else if ($_SERVER['INTERFACE'] === 'administrator') - $this->view->rows = model::list(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer['administrator']['tasks']['page']); - else $this->view->rows = []; + if (isset($parameters['row'])) { + // Запрошена строка - // Запись в cookie (только таким методом можно записать "hostonly: true") - setcookie( - 'tasks_page', - (string) $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['page'], - [ - 'expires' => strtotime('+1 hour'), - 'path' => '/', - 'secure' => true, - 'httponly' => false, - 'samesite' => 'strict' - ] - ); + // Добавление идентификатора строки в фильтр + $filters .= ' && task._key == "' . $parameters['row'] . '"'; + + // Инициализация данных для генерации HTML-документа с таблицей + if ($_SERVER['INTERFACE'] === 'worker') + $this->view->rows = model::list(before: 'FILTER task.worker == "' . account::worker($this->account->number)?->id . '"' . " && ($filters)"); + else if ($_SERVER['INTERFACE'] === 'operator') + $this->view->rows = model::list(before: "FILTER ($filters)"); + else if ($_SERVER['INTERFACE'] === 'market') + $this->view->rows = model::list(before: 'FILTER task.market == "' . $this->market->id . '"' . " && ($filters)"); + else if ($_SERVER['INTERFACE'] === 'administrator') + $this->view->rows = model::list(before: "FILTER ($filters)"); + else $this->view->rows = []; + } else { + // Запрошена страница (множество строк) + + // Реинициализация актуальной страницы + if (isset($parameters['page'])) $this->session->write(['tasks' => ['page' => $parameters['page']]]); + else if (empty($this->session->buffer[$_SERVER['INTERFACE']]['tasks']['page'])) $this->session->write(['tasks' => ['page' => 1]]); + + // Инициализация данных для генерации HTML-документа с таблицей + if ($_SERVER['INTERFACE'] === 'worker') + $this->view->rows = model::list(before: 'FILTER task.worker == "' . account::worker($this->account->number)?->id . '"' . empty($filters) ? null : " && ($filters)", page: (int) $this->session->buffer['worker']['tasks']['page']); + else if ($_SERVER['INTERFACE'] === 'operator') + $this->view->rows = model::list(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer['operator']['tasks']['page']); + else if ($_SERVER['INTERFACE'] === 'market') + $this->view->rows = model::list(before: 'FILTER task.market == "' . $this->market->id . '"' . empty($filters) ? null : " && ($filters)", page: (int) $this->session->buffer['market']['tasks']['page']); + else if ($_SERVER['INTERFACE'] === 'administrator') + $this->view->rows = model::list(before: empty($filters) ? null : "FILTER ($filters)", page: (int) $this->session->buffer['administrator']['tasks']['page']); + else $this->view->rows = []; + + // Запись в cookie (только таким методом можно записать "hostonly: true") + setcookie( + 'tasks_page', + (string) $this->session->buffer[$_SERVER['INTERFACE']]['tasks']['page'], + [ + 'expires' => strtotime('+1 hour'), + 'path' => '/', + 'secure' => true, + 'httponly' => false, + 'samesite' => 'strict' + ] + ); + + // Запись в глобальную переменную шаблонизатора обрабатываемой страницы + $this->view->page = $parameters['page']; + }; // Предобработка строк перед генерацией документа $this->view->rows = static::preprocessing($this->view->rows); - // Запись в глобальную переменную шаблонизатора обрабатываемой страницы - $this->view->page = $parameters['page']; - // Инициализация блока return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'); } @@ -1003,10 +1020,6 @@ final class task extends core // Инициализация строки в глобальную переменную шаблонизатора $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); - // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) - $this->view->page = null; - - // Запись заголовков ответа header('Content-Type: application/json'); header('Content-Encoding: none'); @@ -1045,7 +1058,7 @@ final class task extends core public function commentary(array $parameters = []): void { if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator' || $this->account->type === 'market')) { - // Авторизован аккаунт оператора + // Авторизован аккаунт оператора, администратора или магазина // Инициализация данных $task = model::read('d._key == "' . $parameters['task'] . '"'); @@ -1062,9 +1075,6 @@ final class task extends core // Инициализация строки в глобальную переменную шаблонизатора $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); - // Запись в глобальную переменную шаблонизатора обрабатываемой страницы (отключение) - $this->view->page = null; - // Запись заголовков ответа header('Content-Type: application/json'); header('Content-Encoding: none'); @@ -1092,4 +1102,115 @@ final class task extends core } } } + + /** + * Опубликовать + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function publish(array $parameters = []): void + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора + + // Инициализация данных + $task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($task instanceof _document) { + // Найдена заявка + + // Запись статуса о публикации + $task->published = true; + + if (_core::update($task)) { + // Записано в ArangoDB + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'published' => true, + 'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'), + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } + } + } + } + + /** + * Снять с публикации + * + * @param array $parameters Параметры запроса + * + * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами + */ + public function unpublish(array $parameters = []): void + { + if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) { + // Авторизован аккаунт оператора + + // Инициализация данных + $task = model::read('d._key == "' . $parameters['task'] . '"'); + + if ($task instanceof _document) { + // Найдена заявка + + // Запись статуса о публикации + $task->published = false; + + if (_core::update($task)) { + // Записано в ArangoDB + + // Инициализация строки в глобальную переменную шаблонизатора + $this->view->rows = static::preprocessing(model::list(before: 'FILTER task._key == "' . $parameters['task'] . '"', amount: 1)); + + // Запись заголовков ответа + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Инициализация буфера вывода + ob_start(); + + // Генерация ответа + echo json_encode( + [ + 'unpublished' => true, + 'row' => $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'tasks.html'), + 'errors' => self::parse_only_text($this->errors) + ] + ); + + // Запись заголовков ответа + header('Content-Length: ' . ob_get_length()); + + // Отправка и деинициализация буфера вывода + ob_end_flush(); + flush(); + } + } + } + } + } diff --git a/mirzaev/ebala/system/models/core.php b/mirzaev/ebala/system/models/core.php index 9f6fcb6..4307093 100755 --- a/mirzaev/ebala/system/models/core.php +++ b/mirzaev/ebala/system/models/core.php @@ -88,7 +88,7 @@ class core extends model */ public static function read( string $filter = '', - string $sort = 'd.created DESC', + string $sort = 'd.created DESC, d._key DESC', int $amount = 1, int $page = 1, string $return = 'd', diff --git a/mirzaev/ebala/system/models/registry.php b/mirzaev/ebala/system/models/registry.php index 20a4cdb..eaf0011 100755 --- a/mirzaev/ebala/system/models/registry.php +++ b/mirzaev/ebala/system/models/registry.php @@ -46,7 +46,7 @@ final class registry extends core ?string $before = '', int $amount = 100, int $page = 1, - string $sort = 'worker.created DESC', + string $sort = 'worker.created DESC, worker._key DESC', array &$errors = [] ): array { try { @@ -57,7 +57,7 @@ final class registry extends core $workers = collection::search(static::$arangodb->session, sprintf( <<session, sprintf( <<session, account::COLLECTION)) { + // Инициализирована коллекция + + // Search the session data in ArangoDB + $operators = collection::search(static::$arangodb->session, sprintf( + << $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return []; + } + + /** + * Generate administrators list + * + * @param ?string $before Injection of AQL-code before search + * @param int $amount Amount of administrators + * @param int $page Offset by amount + * @param array $errors Errors registry + * + * @return array Instances from ArangoDB + */ + public static function administrators( + ?string $before = '', + int $amount = 100, + int $page = 1, + string $sort = 'account.created DESC, account._key DESC', + array &$errors = [] + ): array { + try { + if (collection::init(static::$arangodb->session, account::COLLECTION)) { + // Инициализирована коллекция + + // Search the session data in ArangoDB + $administrators = collection::search(static::$arangodb->session, sprintf( + << $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return []; + } } diff --git a/mirzaev/ebala/system/models/task.php b/mirzaev/ebala/system/models/task.php index eb3c922..1bef9a9 100755 --- a/mirzaev/ebala/system/models/task.php +++ b/mirzaev/ebala/system/models/task.php @@ -122,7 +122,7 @@ final class task extends core ?string $after = '', int $amount = 100, int $page = 1, - string $sort = 'task.created DESC', + string $sort = 'task.created DESC, task._key DESC', array &$errors = [] ): array { try { diff --git a/mirzaev/ebala/system/public/css/animations.css b/mirzaev/ebala/system/public/css/animations.css index a810453..5f8dd5f 100755 --- a/mirzaev/ebala/system/public/css/animations.css +++ b/mirzaev/ebala/system/public/css/animations.css @@ -22,6 +22,20 @@ } } +@keyframes row-reinitialized { + 0%, + 20% { + filter: contrast(0.4); + background-color: var(--earth-background); + } + + 50%, + 100% { + filter: unset; + background-color: var(--background, --earth-background-above); + } +} + @keyframes uprise { 0% { opacity: 0; diff --git a/mirzaev/ebala/system/public/css/list.css b/mirzaev/ebala/system/public/css/list.css index af7cc95..64381c1 100755 --- a/mirzaev/ebala/system/public/css/list.css +++ b/mirzaev/ebala/system/public/css/list.css @@ -85,12 +85,23 @@ section.panel.list > div#title > span { } section.panel.list > div.row { + --background: var(--background-above-1); position: relative; + left: 0px; + width: calc(100% - 24px); height: 35px; display: flex; gap: 12px; padding: 0 12px; - background-color: var(--background-above-1); + border-radius: 0px; + background-color: var(--background); +} + +section.panel.list > div.row:not(:nth-of-type(1)):hover { + left: -12px; + padding: 0 24px; + border-radius: 3px; + transition: 0s; } section.panel.list > div.row:first-of-type { @@ -106,7 +117,7 @@ section.panel.list > div.row:hover * { } section.panel.list > div.row:nth-of-type(2n + 1) { - background-color: var(--background-above-2); + --background: var(--background-above-2); } section.panel.list > div.row[data-selected="true"]::before { @@ -149,11 +160,27 @@ section.panel.list > div.row[data-selected="true"]::after { } section.panel.list > div.row.confirmed { - background-color: var(--grass-background-above-2); + --background: var(--grass-background-above-2); } section.panel.list > div.row.confirmed:nth-of-type(2n + 1) { - background-color: var(--grass-background-above-1); + --background: var(--grass-background-above-1); +} + +section.panel.list > div.row.published { + --background: var(--river-background); +} + +section.panel.list > div.row.published:nth-of-type(2n + 1) { + --background: var(--river-background-above); +} + +section.panel.list > div.row.confirmed.published { + --background: var(--sea-background); +} + +section.panel.list > div.row.confirmed.published:nth-of-type(2n + 1) { + --background: var(--sea-background-above); } section.panel.list > div.row.hided { @@ -180,6 +207,25 @@ section.panel.list > div.row.hided:hover * { opacity: unset; } +section.panel.list > div.row.reinitialized { + animation-duration: 3s; + animation-name: row-reinitialized; + animation-timing-function: ease-in; +} + +section.panel.list + > div.row:not(:nth-of-type(1), [data-selected="true"]).reinitializable:before { + content: var(--counter, "0"); + position: absolute; + left: -95px; + align-self: center; + width: 80px; + text-align: right; + font-size: small; + font-weight: bold; + color: var(--earth-text-above); +} + section.panel.list > div.row > span { position: relative; margin: auto 0; diff --git a/mirzaev/ebala/system/public/css/pages/administrators.css b/mirzaev/ebala/system/public/css/pages/administrators.css new file mode 100755 index 0000000..51af5c3 --- /dev/null +++ b/mirzaev/ebala/system/public/css/pages/administrators.css @@ -0,0 +1,63 @@ +section#administrators.panel.list + > div.row:nth-of-type(1) + > span[data-column="end"] + > i.home { + margin-top: 5px; + height: 12px; +} + +section#administrators.panel.list + > div.row + > span:is( + [data-column="id"], + [data-column="name"], + [data-column="number"], + [data-column="mail"], + [data-column="commentary"], + [data-column="status"], + + ) { + min-width: 220px; + width: 220px; + display: ruby; + text-overflow: ellipsis; + overflow: hidden; +} + +section#administrators.panel.list > div.row > span[data-column="id"] { + min-width: 102px; + width: 102px; + font-weight: bold; + text-align: center; +} + +section#administrators.panel.list > div.row:nth-of-type(1) > span[data-column="id"] { + margin-top: 8px; +} + +section#administrators.panel.list > div.row > span[data-column="name"] { + min-width: 180px; + width: 180px; +} + +section#administrators.panel.list > div.row > span[data-column="number"] { + min-width: 130px; + width: 130px; +} + +section#administrators.panel.list > div.row > span[data-column="mail"] { + min-width: 300px; + width: 300px; +} + +section#administrators.panel.list > div.row > span[data-column="commentary"] { + min-width: unset; + width: 100%; +} + +section#administrators.panel.list > div.row > span[data-column="status"] { + min-width: 100px; + width: 100px; + font-size: small; + text-align: center; +} diff --git a/mirzaev/ebala/system/public/css/pages/markets.css b/mirzaev/ebala/system/public/css/pages/markets.css index 2e29afe..e059616 100755 --- a/mirzaev/ebala/system/public/css/pages/markets.css +++ b/mirzaev/ebala/system/public/css/pages/markets.css @@ -34,8 +34,8 @@ section#markets.panel.list > div.row:nth-of-type(1) > span[data-column="id"] { } section#markets.panel.list > div.row > span[data-column="director"] { - min-width: 400px; - width: 400px; + min-width: 180px; + width: 180px; } section#markets.panel.list > div.row > span[data-column="address"] { diff --git a/mirzaev/ebala/system/public/css/pages/operators.css b/mirzaev/ebala/system/public/css/pages/operators.css index 398b375..2dbfd4a 100755 --- a/mirzaev/ebala/system/public/css/pages/operators.css +++ b/mirzaev/ebala/system/public/css/pages/operators.css @@ -11,16 +11,11 @@ section#operators.panel.list > span:is( [data-column="id"], [data-column="name"], - [data-column="birth"], [data-column="number"], - [data-column="passport"], - [data-column="department"], - [data-column="city"], - [data-column="address"], - [data-column="requisites"], - [data-column="tax"], + [data-column="mail"], [data-column="commentary"], [data-column="status"], + ) { min-width: 220px; width: 220px; @@ -30,9 +25,10 @@ section#operators.panel.list } section#operators.panel.list > div.row > span[data-column="id"] { - min-width: 67px; - width: 67px; + min-width: 102px; + width: 102px; font-weight: bold; + text-align: center; } section#operators.panel.list > div.row:nth-of-type(1) > span[data-column="id"] { @@ -40,45 +36,18 @@ section#operators.panel.list > div.row:nth-of-type(1) > span[data-column="id"] { } section#operators.panel.list > div.row > span[data-column="name"] { - min-width: 200px; - width: 200px; -} - -section#operators.panel.list > div.row > span[data-column="birth"] { - min-width: 80px; - width: 80px; - font-size: small; + min-width: 180px; + width: 180px; } section#operators.panel.list > div.row > span[data-column="number"] { - min-width: 120px; - width: 120px; + min-width: 130px; + width: 130px; } -section#operators.panel.list > div.row > span[data-column="passport"] { - min-width: 150px; - width: 150px; -} - -section#operators.panel.list > div.row > span[data-column="city"] { - min-width: 90px; - width: 90px; -} - -section#operators.panel.list > div.row > span[data-column="address"] { - min-width: 180px; - width: 180px; -} - -section#operators.panel.list > div.row > span[data-column="requisites"] { - min-width: 180px; - width: 180px; -} - -section#operators.panel.list > div.row > span[data-column="tax"] { - min-width: 55px; - width: 55px; - font-size: small; +section#operators.panel.list > div.row > span[data-column="mail"] { + min-width: 300px; + width: 300px; } section#operators.panel.list > div.row > span[data-column="commentary"] { diff --git a/mirzaev/ebala/system/public/css/pages/tasks.css b/mirzaev/ebala/system/public/css/pages/tasks.css index c66bf54..6a9c364 100755 --- a/mirzaev/ebala/system/public/css/pages/tasks.css +++ b/mirzaev/ebala/system/public/css/pages/tasks.css @@ -39,8 +39,17 @@ section#tasks.panel.list > div.row:nth-of-type(1) > span[data-column="worker"] { } section#tasks.panel.list > div.row > span[data-column="name"] { - min-width: 200px; - width: 200px; + min-width: 180px; + width: 180px; +} + +section#tasks.panel.list > div.row > span[data-column="task"] { + min-width: 100px; + width: 100px; +} + +section#tasks.panel.list > div.row:not(:nth-of-type(1)) > span[data-column="task"] { + text-align: right; } section#tasks.panel.list > div.row:nth-of-type(1) > span[data-column="start"] { @@ -69,13 +78,17 @@ section#tasks.panel.list > div.row > span[data-column="hours"] { font-size: small; } +section#tasks.panel.list > div.row:not(:nth-of-type(1)) > span[data-column="hours"] { + text-align: center; +} + section#tasks.panel.list > div.row:nth-of-type(1) > span[data-column="market"] { margin-bottom: 8px; } section#tasks.panel.list > div.row > span[data-column="market"] { - min-width: 46px; - width: 46px; + min-width: 60px; + width: 60px; font-weight: bold; } @@ -85,17 +98,25 @@ section#tasks.panel.list > div.row > span[data-column="address"] { } section#tasks.panel.list > div.row > span[data-column="type"] { - min-width: 53px; - width: 53px; + min-width: 80px; + width: 80px; font-size: small; } +section#tasks.panel.list > div.row:not(:nth-of-type(1)) > span[data-column="type"] { + text-align: center; +} + section#tasks.panel.list > div.row > span[data-column="tax"] { - min-width: 55px; - width: 55px; + min-width: 100px; + width: 100px; font-size: small; } +section#tasks.panel.list > div.row:not(:nth-of-type(1)) > span[data-column="tax"] { + text-align: center; +} + section#tasks.panel.list > div.row > span[data-column="commentary"] { min-width: unset; width: 100%; diff --git a/mirzaev/ebala/system/public/index.php b/mirzaev/ebala/system/public/index.php index 3867ec2..03b65c9 100755 --- a/mirzaev/ebala/system/public/index.php +++ b/mirzaev/ebala/system/public/index.php @@ -47,8 +47,10 @@ $router->write('/markets/read', 'market', 'read', 'POST'); $router->write('/markets/list', 'market', 'datalist', 'POST'); $router->write('/operators', 'operator', 'index', 'GET'); $router->write('/operators', 'operator', 'index', 'POST'); -$router->write('/administrators', 'administrators', 'index', 'GET'); -$router->write('/administrators', 'administrators', 'index', 'POST'); +$router->write('/operators/read', 'operator', 'read', 'POST'); +$router->write('/administrators', 'administrator', 'index', 'GET'); +$router->write('/administrators', 'administrator', 'index', 'POST'); +$router->write('/administrators/read', 'administrator', 'read', 'POST'); $router->write('/settings', 'settings', 'index', 'GET'); $router->write('/settings', 'settings', 'index', 'POST'); $router->write('/$id', 'account', 'index', 'GET'); @@ -76,6 +78,8 @@ $router->write('/task/$task/description', 'task', 'description', 'POST'); $router->write('/task/$task/commentary', 'task', 'commentary', 'POST'); $router->write('/task/$task/worker/update', 'task', 'update', 'POST'); $router->write('/task/$task/market/update', 'task', 'update', 'POST'); +$router->write('/task/$task/publish', 'task', 'publish', 'POST'); +$router->write('/task/$task/unpublish', 'task', 'unpublish', 'POST'); $router->write('/elements/menu', 'index', 'menu', 'POST'); // Инициализация ядра diff --git a/mirzaev/ebala/system/public/js/administrators.js b/mirzaev/ebala/system/public/js/administrators.js new file mode 100644 index 0000000..1d206ee --- /dev/null +++ b/mirzaev/ebala/system/public/js/administrators.js @@ -0,0 +1,1082 @@ +"use strict"; + +if (typeof window.administrators !== "function") { + // Not initialized + + // Initialize of the class in global namespace + window.administrators = class administrators { + /** + * Заблокировать функцию закрытия всплывающего окна? + */ + static freeze = false; + + /** + * Тело всплывающего окна (массив) + */ + static body = {}; + + /** + * Инициализирован класс? + */ + static initialized = false; + + /** + * Ожидание зависимости: ядро + * + * @param {function} execute Функция, которая будет встроена в демпфер + * @param {mixed} args Аргументы встраиваемой функции + * + * @return {void} + */ + static core(execute, ...args) { + if (typeof execute === "function") { + // Получена функция + + // Инициализация интервала для ожидания загрузки зависимостей + const interval = setInterval(() => { + if (typeof core === "function") { + // Инициализировано ядро + + // Деинициализация интервала для ожидания загрузки записимостей + clearInterval(interval); + + // Запуск выполнения + return execute(...args); + } + }, 100); + + // Инициализация деинициализации интервала для ожидания загрузки зависимостей спустя большой срок времени + setTimeout(() => clearInterval(interval), 3000); + } + } + + /** + * Ожидание зависимости: демпфер + * + * @param {function} execute Функция, которая будет встроена в демпфер + * @param {number} timer Количество миллисекунд для демпфера + * @param {mixed} args Аргументы встраиваемой функции + * + * @return {void} + */ + static damper(execute, timer = 3000, ...args) { + if (typeof execute === "function") { + // Получена функция + + // Инициализация интервала для ожидания загрузки зависимостей + const interval = setInterval(() => { + if (typeof damper === "function") { + // Инициализирован демпфер + + // Деинициализация интервала для ожидания загрузки записимостей + clearInterval(interval); + + // Запуск выполнения + return damper(() => execute(...args), timer); + } + }, 100); + + // Инициализация деинициализации интервала для ожидания загрузки зависимостей спустя большой срок времени + setTimeout(() => clearInterval(interval), 3000); + } + } + + /** + * Авторизация + * + * @param {function} execute Функция, которая будет встроена в демпфер + * @param {mixed} args Аргументы встраиваемой функции + * + * @return {void} + */ + static authorization(execute, ...args) { + if (typeof execute === "function") { + // Получена функция + + if (core.page === this.page) { + // Пройдена проверка на страницу + + // Запуск выполнения + return execute(...args); + } + } + } + + /** + * Создать заявку + * + * @param {HTMLElement} cashiers Количество кассиров + * @param {HTMLElement} displayers Количество выкладчиков + * @param {HTMLElement} loaders Количество грузчиков + * @param {HTMLElement} gastronomes Количество гастрономов + * @param {HTMLElement} start Начало работы (00:00) + * @param {HTMLElement} end Конец работы (00:00) + * @param {HTMLElement} date Дата работы (d.m.Y) + * @param {HTMLElement} button Кнопка создания заявки + {% endif %} + + + + +
+ + ФИО + Номер + Почта + Комментарий + Статус +
+ + + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/mirzaev/ebala/system/views/pages/markets.html b/mirzaev/ebala/system/views/pages/markets.html index 52581cf..918ef63 100644 --- a/mirzaev/ebala/system/views/pages/markets.html +++ b/mirzaev/ebala/system/views/pages/markets.html @@ -42,6 +42,7 @@
Директор + Номер Адрес Тип Комментарий diff --git a/mirzaev/ebala/system/views/pages/operators.html b/mirzaev/ebala/system/views/pages/operators.html index acbcb05..f9158b5 100644 --- a/mirzaev/ebala/system/views/pages/operators.html +++ b/mirzaev/ebala/system/views/pages/operators.html @@ -42,8 +42,8 @@
ФИО - Адрес - Тип + Номер + Почта Комментарий Статус
diff --git a/mirzaev/ebala/system/views/pages/tasks.html b/mirzaev/ebala/system/views/pages/tasks.html index 12f197c..2aa3dd8 100644 --- a/mirzaev/ebala/system/views/pages/tasks.html +++ b/mirzaev/ebala/system/views/pages/tasks.html @@ -48,8 +48,8 @@ confirmed=='sand' %}title="... и подтверждённые" {% elseif confirmed=='river' %}title="... или подтверждённые" {% endif %}>Подтверждён + waiting=='sand' %}title="... и неподтверждённые" {% elseif waiting=='river' %}title="... или неподтверждённые" + {% endif %}>Неподтверждён