diff --git a/app/composer.json b/app/composer.json index 037efd0d..f0558eda 100644 --- a/app/composer.json +++ b/app/composer.json @@ -18,10 +18,11 @@ } ], "require-dev": { - "phpunit/phpunit": "11.5.0" + "phpunit/phpunit": "11.5.1" }, "require": { - "php": "^8.2" + "php": "^8.2", + "sentry/sentry": "4.10.0" }, "config": { "optimize-autoloader": true, diff --git a/app/composer.lock b/app/composer.lock index 62217c5b..75941499 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,8 +4,609 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "81f5d8cd4c91ef61e0bf8b29402be98c", - "packages": [], + "content-hash": "4a2dcceddbcf47a172c0550e6de5b002", + "packages": [ + { + "name": "guzzlehttp/psr7", + "version": "2.7.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2024-07-18T11:15:46+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "vimeo/psalm": "^4.3 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0" + }, + "time": "2024-11-18T16:19:46+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "sentry/sentry", + "version": "4.10.0", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-php.git", + "reference": "2af937d47d8aadb8dab0b1d7b9557e495dd12856" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/2af937d47d8aadb8dab0b1d7b9557e495dd12856", + "reference": "2af937d47d8aadb8dab0b1d7b9557e495dd12856", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", + "jean85/pretty-package-versions": "^1.5|^2.0.4", + "php": "^7.2|^8.0", + "psr/log": "^1.0|^2.0|^3.0", + "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0" + }, + "conflict": { + "raven/raven": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.4", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", + "monolog/monolog": "^1.6|^2.0|^3.0", + "phpbench/phpbench": "^1.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^8.5|^9.6", + "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", + "vimeo/psalm": "^4.17" + }, + "suggest": { + "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Sentry\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "PHP SDK for Sentry (http://sentry.io)", + "homepage": "http://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "log", + "logging", + "profiling", + "sentry", + "tracing" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-php/issues", + "source": "https://github.com/getsentry/sentry-php/tree/4.10.0" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2024-11-06T07:44:19+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-20T11:17:29+00:00" + } + ], "packages-dev": [ { "name": "myclabs/deep-copy", @@ -245,16 +846,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.7", + "version": "11.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", "shasum": "" }, "require": { @@ -273,7 +874,7 @@ "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.4.1" + "phpunit/phpunit": "^11.5.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -311,7 +912,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8" }, "funding": [ { @@ -319,7 +920,7 @@ "type": "github" } ], - "time": "2024-10-09T06:21:38+00:00" + "time": "2024-12-11T12:34:27+00:00" }, { "name": "phpunit/php-file-iterator", @@ -568,16 +1169,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.0", + "version": "11.5.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7" + "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0569902506a6c0878930b87ea79ec3b50ea563f7", - "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2b94d4f2450b9869fa64a46fd8a6a41997aef56a", + "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a", "shasum": "" }, "require": { @@ -649,7 +1250,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.0" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.1" }, "funding": [ { @@ -665,7 +1266,7 @@ "type": "tidelift" } ], - "time": "2024-12-06T05:57:38+00:00" + "time": "2024-12-11T10:52:48+00:00" }, { "name": "sebastian/cli-parser", @@ -1695,12 +2296,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/app/src/app/Controllers/Admin/ElementController.php b/app/src/app/Controllers/Admin/ElementController.php index 23ff1897..f5631218 100644 --- a/app/src/app/Controllers/Admin/ElementController.php +++ b/app/src/app/Controllers/Admin/ElementController.php @@ -26,7 +26,7 @@ public function index($queryParams) public function create($queryParams) { - $zones = Zone::findAll(); + $zones = Zone::findAll(['name' => 'not null']); $types = TreeType::findAll(); $contracts = Contract::findAll(); $element_types = ElementType::findAll(); diff --git a/app/src/app/Controllers/Admin/InventoryController.php b/app/src/app/Controllers/Admin/InventoryController.php new file mode 100644 index 00000000..8d8ffab6 --- /dev/null +++ b/app/src/app/Controllers/Admin/InventoryController.php @@ -0,0 +1,31 @@ +<?php + +namespace App\Controllers\Admin; + +use App\Core\View; +use App\Models\Contract; +use App\Models\Element; +use App\Models\User; +use App\Models\WorkOrder; + +class InventoryController +{ + public function index($queryParams) + { + $users = User::count(); + $contracts = Contract::count(); + $elements = Element::count(); + $workorders = WorkOrder::count(); + View::render([ + 'view' => 'Admin/Inventory', + 'title' => 'Dashboard', + 'layout' => 'Admin/AdminLayout', + 'data' => [ + 'users' => $users, + 'contracts' => $contracts, + 'elements' => $elements, + 'workorders' => $workorders, + ], + ]); + } +} diff --git a/app/src/app/Controllers/Admin/ZoneController.php b/app/src/app/Controllers/Admin/ZoneController.php index cd916ded..804710bb 100644 --- a/app/src/app/Controllers/Admin/ZoneController.php +++ b/app/src/app/Controllers/Admin/ZoneController.php @@ -4,13 +4,12 @@ use App\Core\View; use App\Models\Zone; -use App\Models\ZonePredefined; class ZoneController { public function index($queryParams) { - $zones = Zone::getPredefinedZonesWithElements(); + $zones = Zone::findAll(['name' => 'not null']); View::render([ 'view' => 'Admin/Zones', 'title' => 'Predefined Zones', @@ -31,7 +30,7 @@ public function create($queryParams) public function store($postData) { - $zone = new ZonePredefined(); + $zone = new Zone(); $zone->name = $postData['name']; $zone->save(); diff --git a/app/src/app/Core/Logger.php b/app/src/app/Core/Logger.php index 4bdebfc2..cc7b1f8d 100644 --- a/app/src/app/Core/Logger.php +++ b/app/src/app/Core/Logger.php @@ -2,15 +2,20 @@ namespace App\Core; +\Sentry\init([ + 'dsn' => getenv('SENTRY_DSN'), + 'environment' => getenv('APP_ENV'), + 'release' => getenv('IMAGE_VERSION'), + // Specify a fixed sample rate + 'traces_sample_rate' => 1.0, + // Set a sampling rate for profiling - this is relative to traces_sample_rate + 'profiles_sample_rate' => 1.0, +]); + class Logger { - public static function log($message, $level = 'info') + public static function log() { - $logFile = getenv('LOG_FILE_PATH'); - $logMessage = strtoupper($level).' - '.date('Y-m-d H:i:s').' - '.$message.PHP_EOL; - if (! file_exists($logFile)) { - touch($logFile); - } - file_put_contents($logFile, $logMessage, FILE_APPEND); + \Sentry\captureLastError(); } } diff --git a/app/src/app/Layouts/Admin/AdminLayout.php b/app/src/app/Layouts/Admin/AdminLayout.php index d2923e97..df73c38e 100644 --- a/app/src/app/Layouts/Admin/AdminLayout.php +++ b/app/src/app/Layouts/Admin/AdminLayout.php @@ -1,130 +1,193 @@ +<?php $currentPath = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title> - <?php echo $title . ' - ' . getenv('APP_NAME'); ?> - </title> + <title><?php echo $title . ' - ' . getenv('APP_NAME'); ?></title> <script src="https://cdn.tailwindcss.com"></script> <link rel="stylesheet" href="/assets/css/app.css"> + <script src="/assets/js/tailwind.js"></script> + <script src="https://kit.fontawesome.com/f80b94bd90.js" crossorigin="anonymous"></script> </head> -<body class="bg-gray-100 font-sans leading-normal tracking-normal"> - - <div class="flex h-screen"> - <!-- Sidebar --> - <aside class="bg-gray-800 w-64 flex-shrink-0"> - <div class="text-white text-2xl font-bold p-4 border-b border-gray-700"> - <?php echo getenv('APP_NAME'); ?> - </div> - <nav class="mt-4"> - <?php $currentPath = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); ?> - <a href="/admin/dashboard" - class="block py-2 px-4 text-white hover:bg-gray-700 <?php echo $currentPath === '/' ? 'bg-gray-700' : ''; ?>"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" - stroke="currentColor" class="w-5 h-5 inline"> - <path stroke-linecap="round" stroke-linejoin="round" - d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z" /> - </svg> - - Dashboard +<body> + <!-- Navigation Bar --> + <div class="border-b-2 border-slate-300"> + <nav class="flex items-center justify-between px-4 py-4 h-16 max-w-7xl mx-auto"> + <!-- Mobile Menu Button (Visible only on small screens) --> + <button id="menuButton" + class="group inline-flex w-12 h-12 text-slate-800 bg-white text-center items-center justify-center rounded transition md:hidden" + aria-pressed="false" + onclick="this.setAttribute('aria-pressed', !(this.getAttribute('aria-pressed') === 'true'))"> + <span class="sr-only">Menu</span> + <svg class="w-6 h-6 fill-current pointer-events-none" viewBox="0 0 16 16" + xmlns="http://www.w3.org/2000/svg"> + <rect + class="origin-center -translate-y-[5px] translate-x-[7px] transition-all duration-300 ease-[cubic-bezier(.5,.85,.25,1.1)] group-[[aria-pressed=true]]:translate-x-0 group-[[aria-pressed=true]]:translate-y-0 group-[[aria-pressed=true]]:rotate-[315deg]" + y="7" width="9" height="2" rx="1"></rect> + <rect + class="origin-center transition-all duration-300 ease-[cubic-bezier(.5,.85,.25,1.8)] group-[[aria-pressed=true]]:rotate-45" + y="7" width="16" height="2" rx="1"></rect> + <rect + class="origin-center translate-y-[5px] transition-all duration-300 ease-[cubic-bezier(.5,.85,.25,1.1)] group-[[aria-pressed=true]]:translate-y-0 group-[[aria-pressed=true]]:rotate-[135deg]" + y="7" width="9" height="2" rx="1"></rect> + </svg> + </button> + <!-- Logo --> + <a href="#"> + <img class="md:block hidden w-48" src="/assets/images/logotip-horizontal.png" alt="Logo"> + <img class="md:hidden block" src="/assets/images/isotip.png" alt="Logo"> + </a> + <!-- Navigation Links (Visible only on large screens) --> + <div class="hidden md:flex gap-6 ml-20"> + <a href="/admin" + class="menu-link <?php echo (strpos($currentPath, '/admin') === 0 && strpos($currentPath, '/admin/inventory') === false) ? 'active' : ''; ?>"> + Gestión </a> - <a href="/admin/users" - class="block py-2 px-4 text-white hover:bg-gray-700 <?php echo $currentPath === '/users' ? 'bg-gray-700' : ''; ?>"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" - stroke="currentColor" class="w-5 h-5 inline"> - <path stroke-linecap="round" stroke-linejoin="round" - d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" /> - </svg> - - Manage Users + <a href="/admin/inventory" + class="menu-link <?php echo ($currentPath === '/admin/inventory') ? 'active' : ''; ?>"> + Inventario </a> - <a href="/admin/work-orders" - class="block py-2 px-4 text-white hover:bg-gray-700 <?php echo $currentPath === '/work-orders' ? 'bg-gray-700' : ''; ?>"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" - stroke="currentColor" class="w-5 h-5 inline"> - <path stroke-linecap="round" stroke-linejoin="round" - d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z" /> - </svg> + </div> - Manage Work Orders - </a> - <a href="/admin/zones" - class="block py-2 px-4 text-white hover:bg-gray-700 <?php echo $currentPath === '/zones' ? 'bg-gray-700' : ''; ?>"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" - stroke="currentColor" class="w-5 h-5 inline"> - <path stroke-linecap="round" stroke-linejoin="round" - d="M9 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z" /> - </svg> - Manage Zones - </a> - <a href="/admin/tree-types" - class="block py-2 px-4 text-white hover:bg-gray-700 <?php echo $currentPath === '/tree-types' ? 'bg-gray-700' : ''; ?>"> + <!-- User Section --> + <div class="flex items-center gap-4 mx-2"> + <!-- User Info Dropdown --> + <div class="relative inline-block text-left"> + <button onclick="toggleSubmenu()" + class="flex items-center gap-2 bg-white px-3 py-2 text-sm text-gray-900 rounded-md shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"> + Contratos + <svg class="-mr-1 size-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" + aria-hidden="true"> + <path fill-rule="evenodd" + d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z" + clip-rule="evenodd" /> + </svg> + </button> + </div> + <!-- Notifications Icon --> + <a href="#" class="hidden md:block text-gray-700 hover:text-blue-600"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" - stroke="currentColor" class="w-5 h-5 inline"> + stroke="currentColor" class="h-6 w-6"> <path stroke-linecap="round" stroke-linejoin="round" - d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /> + d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" /> </svg> - - Manage Tree Types </a> - <a href="/admin/elements" - class="block py-2 px-4 text-white hover:bg-gray-700 <?php echo $currentPath === '/elements' ? 'bg-gray-700' : ''; ?>"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" - stroke="currentColor" class="w-5 h-5 inline"> - <path stroke-linecap="round" stroke-linejoin="round" - d="m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z" /> - </svg> + <!-- User Avatar --> + <div class="relative flex items-center gap-2 cursor-pointer" + onclick="profileDropdown.classList.toggle('hidden')"> + <img class="h-10 rounded-full" src="/assets/images/avatar.png" alt="User Avatar"> + <div class="hidden md:block text-sm relative"> + <span class="block text-gray-700"><?php echo $_SESSION['user']['name']; ?></span> + <span class="block text-gray-500"> + <?php echo ($_SESSION['user']['role'] == 2) ? 'ADMINISTRADOR' : 'USUARIO'; ?> + </span> + <div id="profile-dropdown" + class="hidden absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black/5 focus:outline-none" + role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1"> + <div class="py-1" role="none"> + <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:text-gray-800" + role="menuitem" tabindex="-1" id="menu-item-0">Account settings</a> + <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" + id="menu-item-1">Support</a> + <a href="#" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" + id="menu-item-2">License</a> + <a href="/logout" class="block px-4 py-2 text-sm text-gray-700 hover:text-gray-800" + role="menuitem" tabindex="-1" id="menu-item-3">Sign out</a> + </div> + </div> + </div> + </div> + </div> + </nav> + </div> + <!-- Submenu Section (Hidden by default on small screens, visible on medium and larger screens) --> + <?php if ($currentPath !== '/admin/inventory') { ?> + <div id="submenu" class="md:flex hidden justify-center items-center gap-6 p-6 border-b-2 md:border-b-0"> + <div class="submenu flex space-x-6"> + <!-- Contracts --> + <div class="submenu-item"> + <a href="/admin/contracts" + class="link-primary <?php echo ($currentPath == '/admin/contracts') ? 'active' : ''; ?>"> + <i class="fas fa-file-contract md:block"></i> + <span>Contratos</span> + </a> + </div> - Manage Elements - </a> - <a href="/admin/task-types" - class="block py-2 px-4 text-white hover:bg-gray-700 <?php echo $currentPath === '/task-types' ? 'bg-gray-700' : ''; ?>"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" - stroke="currentColor" class="w-5 h-5 inline"> - <path stroke-linecap="round" stroke-linejoin="round" - d="m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z" /> - </svg> + <!-- Work Orders--> + <div class="submenu-item"> + <a href="/admin/work-orders" + class="link-primary <?php echo ($currentPath == '/admin/work-orders') ? 'active' : ''; ?>"> + <i class="fas fa-briefcase md:block"></i> + <span>Orden de Trabajo</span> + </a> + </div> - Manage Task Types - </a> - <a href="/admin/contracts" - class="block py-2 px-4 text-white hover:bg-gray-700 <?php echo $currentPath === '/contracts' ? 'bg-gray-700' : ''; ?>"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" - stroke="currentColor" class="w-5 h-5 inline"> - <path stroke-linecap="round" stroke-linejoin="round" - d="M12 7.5h1.5m-1.5 3h1.5m-7.5 3h7.5m-7.5 3h7.5m3-9h3.375c.621 0 1.125.504 1.125 1.125V18a2.25 2.25 0 0 1-2.25 2.25M16.5 7.5V18a2.25 2.25 0 0 0 2.25 2.25M16.5 7.5V4.875c0-.621-.504-1.125-1.125-1.125H4.125C3.504 3.75 3 4.254 3 4.875V18a2.25 2.25 0 0 0 2.25 2.25h13.5M6 7.5h3v3H6v-3Z" /> - </svg> + <!-- Zones --> + <div class="submenu-item"> + <a href="/admin/zones" + class="link-primary <?php echo ($currentPath == '/admin/zones') ? 'active' : ''; ?>"> + <i class="fas fa-map-marker-alt md:block"></i> + <span>Zonas</span> + </a> + </div> - Manage Contracts - </a> - </nav> - </aside> + <!-- Elements --> + <div class="submenu-item"> + <a href="/admin/elements" + class="link-primary <?php echo ($currentPath == '/admin/elements') ? 'active' : ''; ?>"> + <i class="fas fa-cube md:block"></i> + <span>Elementos</span> + </a> + </div> + + <!-- Task Types --> + <div class="submenu-item"> + <a href="/admin/task-types" + class="link-primary <?php echo ($currentPath == '/admin/task-types') ? 'active' : ''; ?>"> + <i class="fas fa-tasks md:block"></i> + <span>Tipo Tarea</span> + </a> + </div> + <!-- Users --> + <div class="submenu-item"> + <a href="/admin/users" + class="link-primary <?php echo ($currentPath == '/admin/users') ? 'active' : ''; ?>"> + <i class="fas fa-users md:block"></i> + <span>Usuarios</span> + </a> + </div> - <!-- Main content area --> - <div class="flex-1 flex flex-col"> - <!-- Top bar --> - <header class="bg-white shadow p-4 flex justify-between items-center"> - <div class="text-xl font-bold"><?php echo $title; ?> + <!-- Partes --> + <div class="submenu-item"> + <a href="/admin/work-reports" + class="link-primary <?php echo ($currentPath == '/admin/work-reports') ? 'active' : ''; ?>"> + <i class="fas fa-clipboard-list md:block"></i> + <span>Partes</span> + </a> </div> - <div class="flex items-center space-x-4"> - <span class="text-gray-600">Welcome, - <?php echo $_SESSION['user']['name'] . ' ' . $_SESSION['user']['surname']; ?></span> - <a href="/logout" - class="bg-gray-700 hover:bg-gray-600 text-white font-medium py-2 px-4 rounded-lg shadow focus:outline-none focus:ring focus:ring-green-500"> - Logout</a> + + <!-- Stats --> + <div class="submenu-item"> + <a href="/admin/stats" + class="link-primary <?php echo ($currentPath == '/admin/stats') ? 'active' : ''; ?>"> + <i class="fas fa-chart-bar md:block"></i> + <span>Estadísticas</span> + </a> </div> - </header> + </div> + - <!-- Content area --> - <main class="flex-grow p-6 overflow-auto"> - <?php echo $content; ?> - </main> </div> + <?php } ?> + + <!-- Main Content --> + <div class="max-w-7xl mx-auto px-6"> + <?php echo $content; ?> </div> <script src="/assets/js/app.js"></script> @@ -137,7 +200,17 @@ class="bg-gray-700 hover:bg-gray-600 text-white font-medium py-2 px-4 rounded-lg } }, 3500); </script> + <script> + const menuButton = document.getElementById('menuButton'); + const submenu = document.getElementById('submenu'); + const submenuItems = document.querySelectorAll('.submenu-item'); + const profileDropdown = document.getElementById('profile-dropdown'); + + menuButton.addEventListener('click', () => { + submenu.classList.toggle('hidden'); + }); + </script> </body> -</html> \ No newline at end of file +</html> diff --git a/app/src/app/Layouts/Customer/CustomerLayout.php b/app/src/app/Layouts/Customer/CustomerLayout.php index 17195fcf..2e13e6f6 100644 --- a/app/src/app/Layouts/Customer/CustomerLayout.php +++ b/app/src/app/Layouts/Customer/CustomerLayout.php @@ -5,7 +5,7 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title> - <?php echo htmlspecialchars($title) . ' - ' . htmlspecialchars(getenv('APP_NAME')); ?> + <?php echo $title . ' - ' . getenv('APP_NAME'); ?> </title> <script src="/assets/js/app.js"></script> <script src="https://cdn.tailwindcss.com"></script> diff --git a/app/src/app/Models/BaseModel.php b/app/src/app/Models/BaseModel.php index 8df0f65c..1ded47db 100644 --- a/app/src/app/Models/BaseModel.php +++ b/app/src/app/Models/BaseModel.php @@ -171,8 +171,17 @@ public static function findAll(array $filters = [], bool $includeDeleted = false if (!empty($filters)) { $conditions = []; foreach ($filters as $key => $value) { - $conditions[] = "{$key} = :{$key}"; - $params[$key] = $value; + if (is_string($value) && strtoupper($value) === "NOT NULL") { + // Handle NOT NULL condition + $conditions[] = "{$key} IS NOT NULL"; + } elseif ($value === null || strtoupper($value) === "NULL") { + // Handle NULL condition + $conditions[] = "{$key} IS NULL"; + } else { + // Handle standard equality + $conditions[] = "{$key} = :{$key}"; + $params[$key] = $value; + } } $query .= " WHERE " . implode(' AND ', $conditions); } @@ -182,6 +191,9 @@ public static function findAll(array $filters = [], bool $includeDeleted = false $results = Database::prepareAndExecute($query, $params); + if (!is_array($results)) + $results = []; + return array_map(fn($row) => static::mapDataToModel($row), $results); } diff --git a/app/src/app/Views/Admin/Contracts.php b/app/src/app/Views/Admin/Contracts.php index a122ad14..84a35543 100644 --- a/app/src/app/Views/Admin/Contracts.php +++ b/app/src/app/Views/Admin/Contracts.php @@ -12,28 +12,27 @@ <?php } ?> <div class="mb-4 flex justify-end"> - <a href="/admin/contract/create" - class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg shadow focus:outline-none focus:ring focus:ring-green-500"> + <a href="/admin/contract/create" class="btn-create"> Create Contract </a> </div> -<div class="overflow-x-auto"> - <table class="min-w-full table-fixed bg-white border border-gray-300 rounded-lg shadow-md"> - <thead> - <tr class="bg-gray-700 text-white text-left h-14"> - <th class="px-4 py-2 border-b">ID</th> - <th class="px-4 py-2 border-b">Name</th> - <th class="px-4 py-2 border-b">Start Date</th> - <th class="px-4 py-2 border-b">End Date</th> - <th class="px-4 py-2 border-b">Invoice proposed</th> - <th class="px-4 py-2 border-b">Invoice agreed</th> - <th class="px-4 py-2 border-b">Invoice paid</th> - <th class="px-4 py-2 border-b">Created at</th> - <th class="px-4 py-3 border-b text-center">Actions</th> - +<div class="rounded-lg shadow-md overflow-hidden overflow-x-auto"> + <table class="table-auto w-full text-sm text-left text-gray-700"> + <thead class="bg-darkGray text-white uppercase"> + <tr> + <th class="px-4 py-2">ID</th> + <th class="px-4 py-2">Name</th> + <th class="px-4 py-2">Start Date</th> + <th class="px-4 py-2">End Date</th> + <th class="px-4 py-2">Invoice Proposed</th> + <th class="px-4 py-2">Invoice Agreed</th> + <th class="px-4 py-2">Invoice Paid</th> + <th class="px-4 py-2">Created At</th> + <th class="actions-column">Actions</th> </tr> </thead> + <tbody> <?php foreach ($contracts as $contract) { ?> <tr class="hover:bg-gray-50"> @@ -61,27 +60,28 @@ class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg <td class="px-4 py-2 border-b"> <?php echo $contract->getCreatedAt(); ?> </td> - <td class="px-4 py-3 border-b text-center flex justify-center space-x-4"> - <!-- Edit Button (Pencil Icon) --> - <a href="/admin/contract/<?php echo htmlspecialchars($contract->getId()); ?>/edit" - class="text-blue-500 hover:text-blue-700" title="Edit"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" - stroke="currentColor" class="w-5 h-5"> - <path stroke-linecap="round" stroke-linejoin="round" - d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" /> - </svg> - </a> - <!-- Delete Button (Trash Icon) --> - <a href="/admin/contract/<?php echo htmlspecialchars($contract->getId()); ?>/delete" - onclick="return confirm('Are you sure you want to delete this contract?');" - class="text-red-500 hover:text-red-700" title="Delete"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" - stroke="currentColor" class="w-5 h-5"> - <path stroke-linecap="round" stroke-linejoin="round" - d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> - </svg> - </a> + <td class="px-4 py-3 border-b text-center"> + <div class="flex items-center justify-center space-x-4"> + <a href="/admin/contract/<?php echo htmlspecialchars($contract->getId()); ?>/edit" + class="text-darkGreen hover:scale-110" title="Edit"> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" + stroke="currentColor" class="w-5 h-5"> + <path stroke-linecap="round" stroke-linejoin="round" + d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" /> + </svg> + </a> + <a href="/admin/contract/<?php echo htmlspecialchars($contract->getId()); ?>/delete" + onclick="return confirm('Are you sure you want to delete this contract?');" + class="text-red hover:scale-110" title="Delete"> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" + stroke="currentColor" class="w-5 h-5"> + <path stroke-linecap="round" stroke-linejoin="round" + d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> + </svg> + </a> + </div> </td> + </tr> <?php } ?> </tbody> diff --git a/app/src/app/Views/Admin/Inventory.php b/app/src/app/Views/Admin/Inventory.php new file mode 100644 index 00000000..53fc6e17 --- /dev/null +++ b/app/src/app/Views/Admin/Inventory.php @@ -0,0 +1 @@ +Mapa inventari de l'aplicació diff --git a/app/src/app/Views/Admin/TaskType/Create.php b/app/src/app/Views/Admin/TaskType/Create.php index da54fff5..646786e3 100644 --- a/app/src/app/Views/Admin/TaskType/Create.php +++ b/app/src/app/Views/Admin/TaskType/Create.php @@ -1,5 +1,5 @@ <div class="mb-4 flex justify-end"> - <a href="/task-types" + <a href="/admin/task-types" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg shadow focus:outline-none focus:ring focus:ring-green-500 flex items-center space-x-2"> <!-- Heroicon for return/back (chevron-left) --> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> @@ -11,7 +11,7 @@ class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg <div class="bg-white p-8 border border-gray-300 rounded-lg shadow-md"> <h2 class="text-2xl font-semibold text-gray-800 mb-6">Create TaskType</h2> - <form action="/task-types/store" method="POST" class="space-y-6"> + <form action="/admin/task-types/store" method="POST" class="space-y-6"> <!-- Name --> <div> diff --git a/app/src/app/Views/Admin/TaskTypes.php b/app/src/app/Views/Admin/TaskTypes.php index 1a631f9f..197c31a7 100644 --- a/app/src/app/Views/Admin/TaskTypes.php +++ b/app/src/app/Views/Admin/TaskTypes.php @@ -12,17 +12,17 @@ <?php } ?> <div class="mb-4 flex justify-end"> - <a href="/task-types/create" + <a href="/admin/task-types/create" class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg shadow focus:outline-none focus:ring focus:ring-green-500"> Create Task Type </a> </div> -<div class="overflow-x-auto"> - <table class="min-w-full table-fixed bg-white border border-gray-300 rounded-lg shadow-md"> - <thead> +<div class="rounded-lg shadow-md overflow-hidden overflow-x-auto"> + <table class="table-auto w-full text-sm text-left text-gray-700"> + <thead class="bg-darkGray text-white uppercase"> <tr class="bg-gray-700 text-white text-left h-14"> - <th class="px-4 py-2 border-b">ID</th> + <th class="px-4 py-2 border-b">Name</th> <th class="px-4 py-2 border-b">Actions</th> </tr> @@ -30,15 +30,12 @@ class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg <tbody> <?php foreach ($task_types as $task_type) { ?> <tr class="hover:bg-gray-50"> - <td class="px-4 py-2 border-b"> - <?php echo $task_type->getId(); ?> - </td> <td class="px-4 py-2 border-b"> <?php echo $task_type->name; ?> </td> <td class="px-4 py-3 border-b text-center flex justify-center space-x-4"> <!-- Edit Button (Pencil Icon) --> - <a href="/task-types/<?php echo htmlspecialchars($task_type->getId()); ?>/edit" + <a href="/admin/task-types/<?php echo htmlspecialchars($task_type->getId()); ?>/edit" class="text-blue-500 hover:text-blue-700" title="Edit"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"> @@ -47,7 +44,7 @@ class="text-blue-500 hover:text-blue-700" title="Edit"> </svg> </a> <!-- Delete Button (Trash Icon) --> - <a href="/task-types/<?php echo htmlspecialchars($task_type->getId()); ?>/delete" + <a href="/admin/task-types/<?php echo htmlspecialchars($task_type->getId()); ?>/delete" onclick="return confirm('Are you sure you want to delete this user?');" class="text-red-500 hover:text-red-700" title="Delete"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" @@ -61,4 +58,4 @@ class="text-red-500 hover:text-red-700" title="Delete"> <?php } ?> </tbody> </table> -</div> +</div> \ No newline at end of file diff --git a/app/src/app/Views/Admin/WorkOrders.php b/app/src/app/Views/Admin/WorkOrders.php index 06710d72..6b873245 100644 --- a/app/src/app/Views/Admin/WorkOrders.php +++ b/app/src/app/Views/Admin/WorkOrders.php @@ -18,9 +18,9 @@ class="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 rounded- </a> </div> -<div class="overflow-x-auto"> - <table class="min-w-full table-fixed bg-white border border-gray-300 rounded-lg shadow-md"> - <thead> +<div class="rounded-lg shadow-md overflow-hidden overflow-x-auto"> + <table class="table-auto w-full text-sm text-left text-gray-700"> + <thead class="bg-darkGray text-white uppercase"> <tr class="bg-gray-700 text-white text-left h-14"> <th class="py-2 px-4 border-b">ID</th> <th class="py-2 px-4 border-b">Contracte</th> @@ -93,4 +93,4 @@ class="text-red-500 hover:text-red-700" title="Delete"> <?php } ?> </tbody> </table> -</div> +</div> \ No newline at end of file diff --git a/app/src/app/Views/Admin/Zones.php b/app/src/app/Views/Admin/Zones.php index 7ef0a510..9c8dd856 100644 --- a/app/src/app/Views/Admin/Zones.php +++ b/app/src/app/Views/Admin/Zones.php @@ -12,19 +12,16 @@ <?php } ?> <div class="mb-4 flex justify-end"> - <a href="/zone/create" - class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg shadow focus:outline-none focus:ring focus:ring-green-500"> + <a href="/zone/create" class="btn-create"> Create Zone </a> </div> -<div class="overflow-x-auto"> - <table class="min-w-full table-fixed bg-white border border-gray-300 rounded-lg shadow-md"> - <thead> +<div class="rounded-lg shadow-md overflow-hidden overflow-x-auto"> + <table class="table-auto w-full text-sm text-left text-gray-700"> + <thead class="bg-darkGray text-white uppercase"> <tr class="bg-gray-700 text-white text-left h-14"> - <th class="px-4 py-3 border-b">Zone ID</th> <th class="px-4 py-3 border-b">Name</th> - <th class="px-4 py-3 border-b">Photo</th> <th class="px-4 py-3 border-b">Actions</th> </tr> </thead> @@ -32,26 +29,26 @@ class="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg <?php foreach ($zones as $zone) { ?> <tr class="hover:bg-blue-50"> <td class="px-4 py-3 border-b"> - <?php echo htmlspecialchars($zone->getId()); ?></td> - <td class="px-4 py-3 border-b"> - <?php echo htmlspecialchars($zone->predefined->name); ?></td> - <td class="px-4 py-3 border-b"> - <?php echo htmlspecialchars($zone->predefined->photo()->name); ?> + <?php echo htmlspecialchars($zone->name); ?> </td> <td class="px-4 py-3 border-b text-center flex justify-center space-x-4"> <!-- Edit Button (Pencil Icon) --> <a href="/zone/<?php echo htmlspecialchars($zone->getId()); ?>/edit" class="text-blue-500 hover:text-blue-700" title="Edit"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"> - <path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" /> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" + stroke="currentColor" class="w-5 h-5"> + <path stroke-linecap="round" stroke-linejoin="round" + d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" /> </svg> </a> <!-- Delete Button (Trash Icon) --> <a href="/zone/<?php echo htmlspecialchars($zone->getId()); ?>/delete" onclick="return confirm('Are you sure you want to delete this user?');" class="text-red-500 hover:text-red-700" title="Delete"> - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"> - <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" + stroke="currentColor" class="w-5 h-5"> + <path stroke-linecap="round" stroke-linejoin="round" + d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> </svg> </a> </td> @@ -59,4 +56,4 @@ class="text-red-500 hover:text-red-700" title="Delete"> <?php } ?> </tbody> </table> -</div> +</div> \ No newline at end of file diff --git a/app/src/app/bootstrap.php b/app/src/app/bootstrap.php index 71e7bed1..0497d270 100644 --- a/app/src/app/bootstrap.php +++ b/app/src/app/bootstrap.php @@ -7,10 +7,10 @@ // Start session Session::start(); +// Set the error handler +set_error_handler(function () { + Logger::log(); +}); + // Connect to the database Database::connect(); - -// TODO: Set up global error handler -// set_error_handler(function ($severity, $message, $file, $line) { -// Logger::log("Error: {$message} in {$file} on line {$line}"); -// }); diff --git a/app/src/public/assets/css/app.css b/app/src/public/assets/css/app.css index e69de29b..e8b49a0e 100644 --- a/app/src/public/assets/css/app.css +++ b/app/src/public/assets/css/app.css @@ -0,0 +1,19 @@ +/* Importing Google Fonts */ +@import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"); + +/* Global Styles */ + +body { + font-family: "Poppins", sans-serif; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: "Montserrat", sans-serif; +} + diff --git a/app/src/public/assets/images/avatar.png b/app/src/public/assets/images/avatar.png new file mode 100644 index 00000000..a0a3f8ca Binary files /dev/null and b/app/src/public/assets/images/avatar.png differ diff --git a/app/src/public/assets/images/isotip.png b/app/src/public/assets/images/isotip.png new file mode 100644 index 00000000..3f6856bc Binary files /dev/null and b/app/src/public/assets/images/isotip.png differ diff --git a/app/src/public/assets/images/logotip-horizontal.png b/app/src/public/assets/images/logotip-horizontal.png new file mode 100644 index 00000000..a763b4ee Binary files /dev/null and b/app/src/public/assets/images/logotip-horizontal.png differ diff --git a/app/src/public/assets/js/tailwind.js b/app/src/public/assets/js/tailwind.js new file mode 100644 index 00000000..4beef5d3 --- /dev/null +++ b/app/src/public/assets/js/tailwind.js @@ -0,0 +1,88 @@ +tailwind.config = { + theme: { + extend: { + colors: { + darkGreen: "#008037", + lightGreen: "#7FD959", + gray: "#717171", + darkGray: "#444444", + black: "#222222", + hoverBlack: "#000000", + activeBorder: "#222222", + red: "#FF0000", + }, + fontFamily: { + sans: ["Poppins", "sans-serif"], + // MontserratSans: ["Montserrat", "sans-serif"], + condensed: ["Poppins Condensed", "sans-serif"], + }, + }, + }, + plugins: [ + function ({ addComponents }) { + addComponents({ + ".menu-link": { + "@apply font-bold text-lg text-gray hover:text-darkGray transition-colors relative": + {}, + "&::after": { + "@apply absolute bottom-[-2px] left-0 w-0 h-[2px] bg-darkGray": + {}, + }, + "&:hover::after": { + "@apply w-full": {}, + }, + "&.active": { + "@apply text-darkGray": {}, + }, + "&.active::after": { + "@apply w-full bg-darkGray": {}, + }, + }, + + ".link-primary": { + "@apply text-sm tracking-wide font-medium text-gray font-sans hover:text-darkGray transition-colors relative": + {}, + "&.active": { + "@apply text-darkGray font-bold": {}, + }, + "&::after": { + "@apply content-[''] absolute bottom-[-2px] left-0 w-0 h-[2px] bg-darkGray transition-all duration-300 ease-in-out": + {}, + }, + "&:hover::after": { + "@apply w-full": {}, + }, + "&.active::after": { + "@apply w-full bg-darkGray": {}, + }, + }, + + ".link-secondary": { + "@apply text-gray font-sans hover:text-darkGray transition-colors": + {}, + }, + ".submenu-item": { + "@apply py-2 flex flex-col md:items-center md:text-center": + {}, + }, + ".btn-create": { + "@apply text-darkGreen hover:text-white font-medium py-2 px-4 rounded-lg border-2 border-darkGreen hover:bg-darkGreen focus:outline-none focus:ring-2 focus:ring-darkGreen transition-colors": + {}, + }, + ".font-condensed": { + "@apply font-condensed": {}, + }, + ".box-shadow": { + "@apply rounded-lg shadow-lg bg-hoverGray p-4": {}, + }, + ".box-flat": { + "@apply border border-gray p-4": {}, + }, + ".box-active": { + "@apply border border-darkGreen bg-grayActive text-darkGreen p-4": + {}, + }, + }); + }, + ], +}; diff --git a/app/src/routes/admin.php b/app/src/routes/admin.php index 29fe0b6a..d51f75d4 100644 --- a/app/src/routes/admin.php +++ b/app/src/routes/admin.php @@ -5,6 +5,7 @@ use App\Controllers\Admin\ElementController; use App\Controllers\Admin\ElementTypeController; use App\Controllers\Admin\IncidenceController; +use App\Controllers\Admin\InventoryController; use App\Controllers\Admin\TaskTypeController; use App\Controllers\Admin\TreeTypeController; use App\Controllers\Admin\UserController; @@ -20,6 +21,11 @@ 'method' => 'index', 'middlewares' => [AdminMiddleware::class], ], + '/admin/inventory' => [ + 'controller' => InventoryController::class, + 'method' => 'index', + 'middlewares' => [AdminMiddleware::class], + ], // === Users GET Routes '/admin/users' => [ 'controller' => UserController::class, diff --git a/compose.yml b/compose.yml index cfff00ca..782fc6c5 100644 --- a/compose.yml +++ b/compose.yml @@ -59,7 +59,7 @@ services: - DB_NAME=${DB_NAME} - DB_USER=${DB_USER} - DB_PASS_FILE_PATH=/run/secrets/db_pass - - LOG_FILE_PATH=/var/www/html/storage/logs/app.log + - SENTRY_DSN=${SENTRY_DSN_APP} develop: watch: - action: sync