From 4d29f6bb49451e0f3a2482e6e426435a4ca49646 Mon Sep 17 00:00:00 2001 From: Lucho Date: Wed, 24 Jun 2026 16:30:14 -0300 Subject: [PATCH] =?UTF-8?q?Archivos=20de=20configuraci=C3=B3n=20global,=20?= =?UTF-8?q?dependencias=20y=20assets=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 19 +- README.md | 202 ++++++++-- bootstrap/app.php | 99 ++++- composer.json | 4 +- composer.lock | 884 +++++++++++++++++++++++++++++++++++++++++- package-lock.json | 105 ++++- package.json | 7 +- phpunit.xml | 8 +- public/favicon.ico | Bin 0 -> 31321 bytes resources/css/app.css | 226 +++++++++++ resources/js/app.js | 543 +++++++++++++++++++++++++- 11 files changed, 2046 insertions(+), 51 deletions(-) diff --git a/.env.example b/.env.example index c0660ea..df6e610 100644 --- a/.env.example +++ b/.env.example @@ -4,9 +4,9 @@ APP_KEY= APP_DEBUG=true APP_URL=http://localhost -APP_LOCALE=en -APP_FALLBACK_LOCALE=en -APP_FAKER_LOCALE=en_US +APP_LOCALE=es +APP_FALLBACK_LOCALE=es +APP_FAKER_LOCALE=es_AR APP_MAINTENANCE_DRIVER=file # APP_MAINTENANCE_STORE=database @@ -62,4 +62,17 @@ AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false +BACKUP_DESTINATION_DISK=local +BACKUP_MAIL_TO=admin@example.com +BACKUP_ARCHIVE_PASSWORD= +BACKUP_INCLUDE_ENV=false +BACKUP_MAX_AGE_DAYS=7 +BACKUP_MAX_STORAGE_MB=5000 +BACKUP_KEEP_ALL_DAYS=7 +BACKUP_KEEP_DAILY_DAYS=16 +BACKUP_KEEP_WEEKLY_WEEKS=8 +BACKUP_KEEP_MONTHLY_MONTHS=4 +BACKUP_KEEP_YEARLY_YEARS=2 +BACKUP_MAX_STORAGE_LIMIT_MB=5000 + VITE_APP_NAME="${APP_NAME}" diff --git a/README.md b/README.md index 0165a77..ae1eb15 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,183 @@ -

Laravel Logo

+# Abogadas Litoral -

-Build Status -Total Downloads -Latest Stable Version -License -

+Aplicacion web para gestion de turnos de un estudio juridico, desarrollada con Laravel y Vite. -## About Laravel +## Tabla de contenido -Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: +- [Tecnologias y versiones](#tecnologias-y-versiones) +- [Requisitos previos](#requisitos-previos) +- [Instalacion](#instalacion) +- [Comandos utiles](#comandos-utiles) +- [Testing](#testing) +- [Flujo de trabajo con Git](#flujo-de-trabajo-con-git) +- [Estructura principal](#estructura-principal) +- [Notas](#notas) -- [Simple, fast routing engine](https://laravel.com/docs/routing). -- [Powerful dependency injection container](https://laravel.com/docs/container). -- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. -- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). -- Database agnostic [schema migrations](https://laravel.com/docs/migrations). -- [Robust background job processing](https://laravel.com/docs/queues). -- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). +## Tecnologias y versiones -Laravel is accessible, powerful, and provides tools required for large, robust applications. +Versiones tomadas del proyecto actual: -## Learning Laravel +- PHP: `^8.2` +- Laravel Framework: `^12.0` +- PHPUnit: `^11.5.3` +- Node.js: recomendado `>=20` +- npm: recomendado `>=10` +- Vite: `^7.0.7` +- Bootstrap: `^5.3.8` +- FullCalendar: `^6.1.20` +- Spatie Laravel Backup: `^9.3` +- DomPDF (barryvdh/laravel-dompdf): `^3.1` -Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application. +Archivos fuente de versionado: -If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. +- `composer.json` +- `package.json` -## Laravel Sponsors +## Requisitos previos -We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). +- PHP 8.2 o superior +- Composer 2.x +- Node.js y npm +- Base de datos (segun entorno): + - En `.env.example` la conexion por defecto es `sqlite` + - En `config/database.php` el fallback de Laravel esta en `mysql` -### Premium Partners +## Instalacion -- **[Vehikl](https://vehikl.com)** -- **[Tighten Co.](https://tighten.co)** -- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** -- **[64 Robots](https://64robots.com)** -- **[Curotec](https://www.curotec.com/services/technologies/laravel)** -- **[DevSquad](https://devsquad.com/hire-laravel-developers)** -- **[Redberry](https://redberry.international/laravel-development)** -- **[Active Logic](https://activelogic.com)** +### Opcion rapida (script del proyecto) -## Contributing +```bash +composer run setup +``` -Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). +Este script ejecuta: -## Code of Conduct +1. `composer install` +2. Creacion de `.env` desde `.env.example` (si no existe) +3. `php artisan key:generate` +4. `php artisan migrate --force` +5. `npm install` +6. `npm run build` -In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). +### Opcion manual -## Security Vulnerabilities +```bash +composer install +copy .env.example .env +php artisan key:generate +php artisan migrate +npm install +npm run build +``` -If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. +Si usas Linux/Mac, reemplaza `copy` por: -## License +```bash +cp .env.example .env +``` -The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). +## Comandos utiles + +Levantar entorno de desarrollo completo (servidor, cola, logs y Vite): + +```bash +composer run dev +``` + +Levantar solo backend: + +```bash +php artisan serve +``` + +Levantar solo frontend: + +```bash +npm run dev +``` + +Compilar assets para produccion: + +```bash +npm run build +``` + +## Testing + +Ejecutar tests: + +```bash +composer run test +``` + +o + +```bash +php artisan test +``` + +## Flujo de trabajo con Git + +### 1) Crear rama de trabajo + +```bash +git checkout main +git pull origin main +git checkout -b feature/nombre-cambio +``` + +### 2) Commits pequenos y descriptivos + +Formato recomendado: + +- `feat: agrega agenda semanal` +- `fix: corrige validacion de telefono` +- `docs: actualiza readme de instalacion` + +### 3) Subir rama y abrir Pull Request + +```bash +git add . +git commit -m "feat: descripcion breve" +git push -u origin feature/nombre-cambio +``` + +### 4) Actualizar tu rama con cambios de main + +```bash +git checkout main +git pull origin main +git checkout feature/nombre-cambio +git merge main +``` + +## Estructura principal + +- `app/`: modelos, controladores, middleware y logica principal +- `resources/views/`: vistas Blade +- `resources/js/` y `resources/css/`: frontend +- `routes/`: rutas web, api y consola +- `database/migrations/`: migraciones +- `tests/`: pruebas unitarias y feature +- `scripts/`: utilidades de scheduler en Windows + +## Datos de acceso (Entorno de desarrollo) + +Las credenciales de administrador se generan automáticamente al ejecutar las migraciones con seeders: + +```bash +php artisan migrate:fresh --seed +``` + +O si prefieres solo los seeders sin resetear migraciones: + +```bash +php artisan db:seed +``` + +Consulta `database/seeders/` para ver los datos que se cargan. + +## Notas + +- No subir archivos sensibles al repositorio (`.env`, claves, tokens). +- Mantener este README actualizado ante cambios de version o arquitectura. diff --git a/bootstrap/app.php b/bootstrap/app.php index c183276..5b4fae0 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -1,8 +1,15 @@ withRouting( @@ -11,8 +18,96 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - // + $middleware->append(\App\Http\Middleware\SecurityHeaders::class); }) ->withExceptions(function (Exceptions $exceptions): void { - // + $exceptions->report(function (Throwable $exception): void { + if ($exception instanceof ThrottleRequestsException + || $exception instanceof ValidationException + || $exception instanceof HttpResponseException) { + return; + } + + if ($exception instanceof HttpExceptionInterface + && in_array($exception->getStatusCode(), [401, 403, 404, 419, 422, 429], true)) { + return; + } + + try { + $codigo = (string) $exception->getCode(); + if ($codigo === '') { + $codigo = class_basename($exception); + } + + $url = app()->runningInConsole() + ? 'console' + : (string) request()?->fullUrl(); + + Error::query()->create([ + 'codigo' => mb_substr($codigo, 0, 255), + 'mensaje' => mb_substr((string) ($exception->getMessage() ?: class_basename($exception)), 0, 65000), + 'track_trace' => mb_substr($exception->getTraceAsString(), 0, 65000), + 'url' => mb_substr($url !== '' ? $url : 'desconocida', 0, 255), + 'fecha_hora' => now(), + ]); + } catch (Throwable $loggingException) { + // Evita que un error al registrar el error rompa el flujo original. + } + }); + + $exceptions->render(function (ThrottleRequestsException $exception, Request $request) { + $retryAfter = (int) ($exception->getHeaders()['Retry-After'] ?? 60); + $retryAfter = max($retryAfter, 1); + + $minutos = intdiv($retryAfter, 60); + $segundos = $retryAfter % 60; + $partesTiempo = []; + + if ($minutos > 0) { + $partesTiempo[] = $minutos . ' minuto' . ($minutos === 1 ? '' : 's'); + } + + if ($segundos > 0 || empty($partesTiempo)) { + $partesTiempo[] = $segundos . ' segundo' . ($segundos === 1 ? '' : 's'); + } + + $mensaje = 'Demasiados intentos. Esperá ' . implode(' y ', $partesTiempo) . ' antes de volver a intentar.'; + + if ($request->expectsJson()) { + return response()->json([ + 'message' => $mensaje, + 'retry_after' => $retryAfter, + ], 429, $exception->getHeaders()); + } + + $sessionKey = $request->is('login/*') ? 'login_error' : 'recuperar_error'; + + return back() + ->withInput($request->except([ + 'contra', + 'contra_confirmation', + 'contra_actual', + 'contra_actual_secreta', + ])) + ->with($sessionKey, $mensaje); + }); + + $exceptions->render(function (TokenMismatchException $exception, Request $request) { + $mensaje = 'La sesión cambió en otra pestaña o expiró. Recargá la página y volvé a intentar.'; + + if ($request->expectsJson()) { + return response()->json([ + 'message' => $mensaje, + ], 419); + } + + return back() + ->withInput($request->except([ + 'contra', + 'contra_confirmation', + 'contra_actual', + 'contra_actual_secreta', + ])) + ->withErrors(['csrf' => $mensaje]); + }); })->create(); diff --git a/composer.json b/composer.json index 52a3a8a..4f3a822 100644 --- a/composer.json +++ b/composer.json @@ -7,8 +7,10 @@ "license": "MIT", "require": { "php": "^8.2", + "barryvdh/laravel-dompdf": "^3.1", "laravel/framework": "^12.0", - "laravel/tinker": "^2.10.1" + "laravel/tinker": "^2.10.1", + "spatie/laravel-backup": "^9.3" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index 7ba63ad..cb29b5a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,85 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c514d8f7b9fc5970bdd94287905ef584", + "content-hash": "6a842c6b26c34979722aa85fd18609e1", "packages": [ + { + "name": "barryvdh/laravel-dompdf", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-dompdf.git", + "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc", + "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc", + "shasum": "" + }, + "require": { + "dompdf/dompdf": "^3.0", + "illuminate/support": "^9|^10|^11|^12|^13.0", + "php": "^8.1" + }, + "require-dev": { + "larastan/larastan": "^2.7|^3.0", + "orchestra/testbench": "^7|^8|^9.16|^10|^11.0", + "phpro/grumphp": "^2.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf", + "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf" + }, + "providers": [ + "Barryvdh\\DomPDF\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\DomPDF\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "A DOMPDF Wrapper for Laravel", + "keywords": [ + "dompdf", + "laravel", + "pdf" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-dompdf/issues", + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.2" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2026-02-21T08:51:10+00:00" + }, { "name": "brick/math", "version": "0.14.8", @@ -377,6 +454,161 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "dompdf/dompdf", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" + }, + "time": "2026-03-03T13:54:37+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" + }, + "time": "2026-01-20T14:10:26+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4 || ^9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" + }, + "time": "2026-01-02T16:01:13+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.6.0", @@ -2019,6 +2251,73 @@ ], "time": "2026-01-15T06:54:53+00:00" }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, { "name": "monolog/monolog", "version": "3.10.0", @@ -3294,6 +3593,446 @@ }, "time": "2025-12-14T04:43:48+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "v9.3.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.32 || 2.1.32", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpunit/phpunit": "8.5.52", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.2.8", + "rector/type-perfect": "1.0.0 || 2.1.0", + "squizlabs/php_codesniffer": "4.0.1", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.4.x-dev" + } + }, + "autoload": { + "files": [ + "src/Rule/Rule.php", + "src/RuleSet/RuleContainer.php" + ], + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" + }, + "time": "2026-03-03T17:31:43+00:00" + }, + { + "name": "spatie/db-dumper", + "version": "3.8.3", + "source": { + "type": "git", + "url": "https://github.com/spatie/db-dumper.git", + "reference": "eac3221fbe27fac51f388600d27b67b1b079406e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/eac3221fbe27fac51f388600d27b67b1b079406e", + "reference": "eac3221fbe27fac51f388600d27b67b1b079406e", + "shasum": "" + }, + "require": { + "php": "^8.0", + "symfony/process": "^5.0|^6.0|^7.0|^8.0" + }, + "require-dev": { + "pestphp/pest": "^1.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\DbDumper\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Dump databases", + "homepage": "https://github.com/spatie/db-dumper", + "keywords": [ + "database", + "db-dumper", + "dump", + "mysqldump", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/db-dumper/tree/3.8.3" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-01-05T16:26:03+00:00" + }, + { + "name": "spatie/laravel-backup", + "version": "9.3.6", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-backup.git", + "reference": "d378a07b580aa8bf440b50decdbab7b5d6f63c46" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-backup/zipball/d378a07b580aa8bf440b50decdbab7b5d6f63c46", + "reference": "d378a07b580aa8bf440b50decdbab7b5d6f63c46", + "shasum": "" + }, + "require": { + "ext-zip": "^1.14.0", + "illuminate/console": "^10.10.0|^11.0|^12.0", + "illuminate/contracts": "^10.10.0|^11.0|^12.0", + "illuminate/events": "^10.10.0|^11.0|^12.0", + "illuminate/filesystem": "^10.10.0|^11.0|^12.0", + "illuminate/notifications": "^10.10.0|^11.0|^12.0", + "illuminate/support": "^10.10.0|^11.0|^12.0", + "league/flysystem": "^3.0", + "php": "^8.2", + "spatie/db-dumper": "^3.8", + "spatie/laravel-package-tools": "^1.6.2", + "spatie/laravel-signal-aware-command": "^1.2|^2.0", + "spatie/temporary-directory": "^2.0", + "symfony/console": "^6.0|^7.0", + "symfony/finder": "^6.0|^7.0" + }, + "require-dev": { + "composer-runtime-api": "^2.0", + "ext-pcntl": "*", + "larastan/larastan": "^2.7.0|^3.0", + "laravel/slack-notification-channel": "^2.5|^3.0", + "league/flysystem-aws-s3-v3": "^2.0|^3.0", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^8.0|^9.0|^10.0", + "pestphp/pest": "^1.20|^2.0|^3.0|^4.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "rector/rector": "^1.1" + }, + "suggest": { + "laravel/slack-notification-channel": "Required for sending notifications via Slack" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Backup\\BackupServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Helpers/functions.php" + ], + "psr-4": { + "Spatie\\Backup\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "A Laravel package to backup your application", + "homepage": "https://github.com/spatie/laravel-backup", + "keywords": [ + "backup", + "database", + "laravel-backup", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-backup/issues", + "source": "https://github.com/spatie/laravel-backup/tree/9.3.6" + }, + "funding": [ + { + "url": "https://github.com/sponsors/spatie", + "type": "github" + }, + { + "url": "https://spatie.be/open-source/support-us", + "type": "other" + } + ], + "time": "2025-11-05T11:25:01+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.93.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", + "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "php": "^8.1" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^8.0|^9.2|^10.0|^11.0", + "pestphp/pest": "^2.1|^3.1|^4.0", + "phpunit/php-code-coverage": "^10.0|^11.0|^12.0", + "phpunit/phpunit": "^10.5|^11.5|^12.5", + "spatie/pest-plugin-test-time": "^2.2|^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-02-21T12:49:54+00:00" + }, + { + "name": "spatie/laravel-signal-aware-command", + "version": "2.1.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-signal-aware-command.git", + "reference": "54dcc1efd152bfb3eb0faf56a5fc28569b864b5d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-signal-aware-command/zipball/54dcc1efd152bfb3eb0faf56a5fc28569b864b5d", + "reference": "54dcc1efd152bfb3eb0faf56a5fc28569b864b5d", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^11.0|^12.0|^13.0", + "php": "^8.2", + "spatie/laravel-package-tools": "^1.4.3", + "symfony/console": "^7.0|^8.0" + }, + "require-dev": { + "brianium/paratest": "^6.2|^7.0", + "ext-pcntl": "*", + "nunomaduro/collision": "^5.3|^6.0|^7.0|^8.0", + "orchestra/testbench": "^9.0|^10.0", + "pestphp/pest-plugin-laravel": "^1.3|^2.0|^3.0", + "phpunit/phpunit": "^9.5|^10|^11", + "spatie/laravel-ray": "^1.17" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Signal": "Spatie\\SignalAwareCommand\\Facades\\Signal" + }, + "providers": [ + "Spatie\\SignalAwareCommand\\SignalAwareCommandServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\SignalAwareCommand\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Handle signals in artisan commands", + "homepage": "https://github.com/spatie/laravel-signal-aware-command", + "keywords": [ + "laravel", + "laravel-signal-aware-command", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-signal-aware-command/issues", + "source": "https://github.com/spatie/laravel-signal-aware-command/tree/2.1.2" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-02-22T08:16:31+00:00" + }, + { + "name": "spatie/temporary-directory", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/temporary-directory.git", + "reference": "662e481d6ec07ef29fd05010433428851a42cd07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/662e481d6ec07ef29fd05010433428851a42cd07", + "reference": "662e481d6ec07ef29fd05010433428851a42cd07", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\TemporaryDirectory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily create, use and destroy temporary directories", + "homepage": "https://github.com/spatie/temporary-directory", + "keywords": [ + "php", + "spatie", + "temporary-directory" + ], + "support": { + "issues": "https://github.com/spatie/temporary-directory/issues", + "source": "https://github.com/spatie/temporary-directory/tree/2.3.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-01-12T07:42:22+00:00" + }, { "name": "symfony/clock", "version": "v7.4.0", @@ -5795,6 +6534,149 @@ ], "time": "2026-02-15T10:53:20+00:00" }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.4.0", diff --git a/package-lock.json b/package-lock.json index 82278ee..2cf9efe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,8 +5,13 @@ "packages": { "": { "dependencies": { + "@fullcalendar/core": "^6.1.20", + "@fullcalendar/daygrid": "^6.1.20", + "@fullcalendar/interaction": "^6.1.20", + "@fullcalendar/timegrid": "^6.1.20", "@popperjs/core": "^2.11.8", - "bootstrap": "^5.3.8" + "bootstrap": "^5.3.8", + "html2canvas": "^1.4.1" }, "devDependencies": { "axios": "^1.11.0", @@ -457,6 +462,45 @@ "node": ">=18" } }, + "node_modules/@fullcalendar/core": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.20.tgz", + "integrity": "sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==", + "license": "MIT", + "dependencies": { + "preact": "~10.12.1" + } + }, + "node_modules/@fullcalendar/daygrid": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.20.tgz", + "integrity": "sha512-AO9vqhkLP77EesmJzuU+IGXgxNulsA8mgQHynclJ8U70vSwAVnbcLG9qftiTAFSlZjiY/NvhE7sflve6cJelyQ==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.20" + } + }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.20.tgz", + "integrity": "sha512-p6txmc5txL0bMiPaJxe2ip6o0T384TyoD2KGdsU6UjZ5yoBlaY+dg7kxfnYKpYMzEJLG58n+URrHr2PgNL2fyA==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.20" + } + }, + "node_modules/@fullcalendar/timegrid": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.20.tgz", + "integrity": "sha512-4H+/MWbz3ntA50lrPif+7TsvMeX3R1GSYjiLULz0+zEJ7/Yfd9pupZmAwUs/PBpA6aAcFmeRr0laWfcz1a9V1A==", + "license": "MIT", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.20" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.20" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -908,6 +952,15 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/bootstrap": { "version": "5.3.8", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", @@ -1044,6 +1097,15 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1372,6 +1434,19 @@ "node": ">= 0.4" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1503,6 +1578,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -1642,6 +1727,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -1676,6 +1770,15 @@ "dev": true, "license": "0BSD" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", diff --git a/package.json b/package.json index 6fe117d..dcf4da1 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,12 @@ "vite": "^7.0.7" }, "dependencies": { + "@fullcalendar/core": "^6.1.20", + "@fullcalendar/daygrid": "^6.1.20", + "@fullcalendar/interaction": "^6.1.20", + "@fullcalendar/timegrid": "^6.1.20", "@popperjs/core": "^2.11.8", - "bootstrap": "^5.3.8" + "bootstrap": "^5.3.8", + "html2canvas": "^1.4.1" } } diff --git a/phpunit.xml b/phpunit.xml index d703241..2836cf5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -23,8 +23,12 @@ - - + + + + + + diff --git a/public/favicon.ico b/public/favicon.ico index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..27fd382a92690c942f3072a712b2ba12c103f25c 100644 GIT binary patch literal 31321 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 N=;;_~SDhzD{2$+#wz&WR literal 0 HcmV?d00001 diff --git a/resources/css/app.css b/resources/css/app.css index f51f054..360e285 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1 +1,227 @@ @import 'bootstrap/dist/css/bootstrap.min.css'; + +html { + font-size: 18px; +} + +.app-navbar { + background: #ffb555; + border-bottom: 1px solid #ffb13c; +} + +.app-navbar .navbar-toggler { + border-color: rgba(255, 255, 255, 0.45); +} + +.app-navbar .navbar-toggler:focus { + box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.2); +} + +.app-navbar .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.92%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} + +.app-navbar .navbar-nav { + gap: 0.5rem; +} + +.app-navbar-link { + color: #4d4545; + background: transparent; + border: 2px solid transparent; + font-weight: 600; +} + +.app-navbar-link:hover, +.app-navbar-link:focus, +.app-navbar-link:active { + color: #352e2e; + background: rgba(255, 255, 255, 0.16); + border-color: rgba(255, 255, 255, 0.24); +} + +.app-navbar-link.active, +.app-navbar-link[aria-current='page'] { + color: #fff; + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.3); +} + +.app-navbar .navbar-brand, +.app-navbar .navbar-brand:hover, +.app-navbar .navbar-brand:focus { + color: #fff; + font-weight: 700; +} + +.app-navbar-greeting { + color: #fff; + font-size: 0.95rem; + font-weight: 700; + white-space: nowrap; +} + +@media (max-width: 991.98px) { + .app-navbar-greeting { + display: none; + } +} + +.app-footer { + background: #ff9720; + border-top: 1px solid #ec7f1a; + color: #fff; +} + +.app-footer h3, +.app-footer p, +.app-footer .small { + color: inherit; +} + +.app-footer a { + color: #fff; + text-decoration: none; + border-bottom: 1px solid rgba(255, 255, 255, 0.35); +} + +.app-footer a:hover, +.app-footer a:focus { + color: #fff; + border-bottom-color: rgba(255, 255, 255, 0.85); +} + +.servicio-card-img { + width: 100%; + height: 200px; + object-fit: cover; + object-position: center; +} + +html.sidebar-nav-pending .app-navbar #menuPrincipal > ul, +html.sidebar-nav-pending .app-navbar .navbar-toggler { + visibility: hidden; + pointer-events: none; +} + +/* Evita el flash del menu horizontal en pantallas que luego se convierten a sidebar. */ +.app-navbar #menuPrincipal > ul:has(a[href^='/administrador/']), +.app-navbar #menuPrincipal > ul:has(a[href^='/profesional/']) { + visibility: hidden; + pointer-events: none; +} + +body:has(.app-navbar #menuPrincipal > ul:has(a[href^='/administrador/'])) .app-navbar .navbar-toggler, +body:has(.app-navbar #menuPrincipal > ul:has(a[href^='/profesional/'])) .app-navbar .navbar-toggler { + visibility: hidden; + pointer-events: none; +} + +/* Reserva el ancho del sidebar antes de que JS agregue clases y evita el "flash" de layout full-width. */ +@media (min-width: 992px) { + body:has(.app-navbar #menuPrincipal > ul:has(a[href^='/administrador/'])) main, + body:has(.app-navbar #menuPrincipal > ul:has(a[href^='/profesional/'])) main { + margin-left: 250px; + width: calc(100% - 250px); + } + + html.sidebar-nav-pending main.sidebar-main-pending { + visibility: hidden; + } +} + +.admin-sidebar { + position: fixed; + top: var(--admin-navbar-height, 74px); + left: 0; + width: 250px; + height: calc(100vh - var(--admin-navbar-height, 74px)); + overflow-y: auto; + background: #ffffff; + border-right: 1px solid #f0d39c; + padding: 1rem 0.75rem; + z-index: 1030; +} + +.admin-sidebar-nav { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.admin-sidebar-link { + text-align: left; + width: 100%; +} + +.admin-sidebar-details { + width: 100%; +} + +.admin-sidebar-details > summary { + list-style: none; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; +} + +.admin-sidebar-details > summary::-webkit-details-marker { + display: none; +} + +.admin-sidebar-details > summary::after { + content: '\203A'; + font-size: 1.1rem; + line-height: 1; + transition: transform 0.2s ease; + display: inline-block; +} + +.admin-sidebar-details[open] > summary::after { + transform: rotate(90deg); +} + +.admin-sidebar-sublink { + padding-left: 1.5rem !important; + font-size: 0.9em; + width: 100%; + text-align: left; +} + +.admin-sidebar .admin-sidebar-link:hover, +.admin-sidebar .admin-sidebar-link:focus, +.admin-sidebar .admin-sidebar-link:active { + color: #352e2e; + background: #f6f7f9; + border-color: #e6e9ef; +} + +.admin-sidebar .admin-sidebar-link.active, +.admin-sidebar .admin-sidebar-link[aria-current='page'] { + color: #352e2e; + background: #ffe0b5; + border-color: #ffb555; +} + +.admin-main-content { + margin-left: 250px; + width: calc(100% - 250px); +} + +@media (max-width: 991.98px) { + .admin-sidebar { + position: static; + top: auto; + left: auto; + width: 100%; + height: auto; + border-right: 0; + border-bottom: 1px solid #f0d39c; + } + + .admin-main-content { + margin-left: 0; + width: 100%; + } +} diff --git a/resources/js/app.js b/resources/js/app.js index a491cf4..c96989f 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,2 +1,543 @@ +// Confirmación de envío de formulario en welcome +document.addEventListener('DOMContentLoaded', function () { + const form = document.querySelector('form[action="/formulario"]'); + const btnConfirmar = document.getElementById('btnConfirmarEnvioForm'); + if (form && btnConfirmar) { + btnConfirmar.addEventListener('click', function () { + form.submit(); + }); + } + + // Confirmación de envío de formulario en dashboard cliente + const formCliente = document.querySelector('form[action="/cliente/formulario"]'); + const btnConfirmarCliente = document.getElementById('btnConfirmarEnvioFormCliente'); + if (formCliente && btnConfirmarCliente) { + btnConfirmarCliente.addEventListener('click', function () { + formCliente.submit(); + }); + } +}); import './bootstrap'; -import 'bootstrap'; +import * as bootstrap from 'bootstrap'; +import html2canvas from 'html2canvas'; +import { Calendar } from '@fullcalendar/core'; +import dayGridPlugin from '@fullcalendar/daygrid'; +import interactionPlugin from '@fullcalendar/interaction'; +import esLocale from '@fullcalendar/core/locales/es'; + +window.html2canvas = html2canvas; +import timeGridPlugin from '@fullcalendar/timegrid'; + +window.FullCalendar = { Calendar }; +window.FullCalendarPlugins = { + dayGridPlugin, + interactionPlugin, + timeGridPlugin, +}; +window.FullCalendarLocales = { + es: esLocale, +}; +window.bootstrap = bootstrap; + +document.addEventListener('DOMContentLoaded', function () { + const carouseles = document.querySelectorAll('.carousel'); + if (!carouseles.length) { + return; + } + + carouseles.forEach((carouselEl) => { + const totalItems = carouselEl.querySelectorAll('.carousel-item').length; + if (totalItems <= 1) { + return; + } + + const instancia = bootstrap.Carousel.getOrCreateInstance(carouselEl, { + interval: 10000, + ride: 'carousel', + pause: 'hover', + wrap: true, + touch: true, + }); + + instancia.cycle(); + }); +}); + +const requiereSidebarLateral = + window.location.pathname.startsWith('/administrador') || + window.location.pathname.startsWith('/profesional'); + +const mainSidebarTarget = requiereSidebarLateral ? document.querySelector('main') : null; + +const limpiarEstadoPendingSidebar = () => { + document.documentElement.classList.remove('sidebar-nav-pending'); + mainSidebarTarget?.classList.remove('sidebar-main-pending'); +}; + +if (requiereSidebarLateral) { + if (mainSidebarTarget) { + mainSidebarTarget.classList.add('admin-main-content', 'sidebar-main-pending'); + } + + document.documentElement.classList.add('sidebar-nav-pending'); + window.setTimeout(() => { + limpiarEstadoPendingSidebar(); + }, 900); +} + +const asegurarFavicon = () => { + const faviconHref = '/favicon.ico?v=20260403'; + const rels = ['icon', 'shortcut icon', 'apple-touch-icon']; + + rels.forEach((rel) => { + let link = document.head?.querySelector(`link[rel="${rel}"]`); + + if (!link) { + link = document.createElement('link'); + link.setAttribute('rel', rel); + document.head?.appendChild(link); + } + + link.setAttribute('type', 'image/x-icon'); + link.setAttribute('href', faviconHref); + }); +}; + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', asegurarFavicon, { once: true }); +} else { + asegurarFavicon(); +} + +document.addEventListener('DOMContentLoaded', function () { + const mapWidgets = document.querySelectorAll('[data-map-widget]'); + if (!mapWidgets.length) { + return; + } + + const updateMapWidget = (widget) => { + const iframe = widget.querySelector('[data-map-embed]'); + const fallback = widget.querySelector('[data-map-fallback]'); + if (!iframe) { + return; + } + + const mapSrc = iframe.getAttribute('data-map-src') || ''; + const offline = !navigator.onLine; + + if (offline) { + iframe.classList.add('d-none'); + fallback?.classList.remove('d-none'); + return; + } + + if (mapSrc && iframe.getAttribute('src') !== mapSrc) { + iframe.setAttribute('src', mapSrc); + } + + iframe.classList.remove('d-none'); + fallback?.classList.add('d-none'); + }; + + mapWidgets.forEach((widget) => { + const iframe = widget.querySelector('[data-map-embed]'); + if (iframe) { + iframe.addEventListener('error', () => { + iframe.classList.add('d-none'); + widget.querySelector('[data-map-fallback]')?.classList.remove('d-none'); + }); + } + + updateMapWidget(widget); + }); + + window.addEventListener('online', () => { + mapWidgets.forEach(updateMapWidget); + }); + + window.addEventListener('offline', () => { + mapWidgets.forEach(updateMapWidget); + }); +}); + +const STORAGE_KEY_NOTIF_CERRADAS = 'notif_cerradas_profesional'; +const STORAGE_KEY_NOTIF_VISTAS = 'notif_vistas_profesional'; + +function getNotificacionesCerradas() { + try { + return JSON.parse(localStorage.getItem(STORAGE_KEY_NOTIF_CERRADAS) || '[]'); + } catch { + return []; + } +} + +function guardarNotificacionesCerradas(claves) { + localStorage.setItem(STORAGE_KEY_NOTIF_CERRADAS, JSON.stringify(claves)); +} + +function getNotificacionesVistas() { + try { + const vistas = JSON.parse(localStorage.getItem(STORAGE_KEY_NOTIF_VISTAS) || '[]'); + return Array.from(new Set([...(Array.isArray(vistas) ? vistas : []), ...getNotificacionesCerradas()])); + } catch { + return getNotificacionesCerradas(); + } +} + +function guardarNotificacionesVistas(claves) { + localStorage.setItem(STORAGE_KEY_NOTIF_VISTAS, JSON.stringify(Array.from(new Set(claves)))); +} + +function marcarNotificacionesComoVistas(clavesActivas) { + if (!Array.isArray(clavesActivas) || !clavesActivas.length) { + return; + } + + const vistasActuales = getNotificacionesVistas(); + guardarNotificacionesVistas([...vistasActuales, ...clavesActivas]); +} + +function podarNotificacionesVistas(clavesActivas) { + const vistas = getNotificacionesVistas(); + if (!vistas.length) { + return []; + } + + const vistasVigentes = vistas.filter((clave) => clavesActivas.includes(clave)); + if (vistasVigentes.length !== vistas.length) { + guardarNotificacionesVistas(vistasVigentes); + } + + return vistasVigentes; +} + +function actualizarBadgeNotificaciones() { + const enlace = document.querySelector('a[href="/profesional/notificaciones"][data-notificaciones-claves]'); + if (!enlace) { + return; + } + + const badge = enlace.querySelector('.js-notificaciones-badge'); + if (!badge) { + return; + } + + let clavesActivas = []; + try { + clavesActivas = JSON.parse(enlace.dataset.notificacionesClaves || '[]'); + } catch { + clavesActivas = []; + } + + if (window.location.pathname === '/profesional/notificaciones') { + marcarNotificacionesComoVistas(clavesActivas); + } + + const vistas = podarNotificacionesVistas(clavesActivas); + const tienePendientes = clavesActivas.some((clave) => !vistas.includes(clave)); + + badge.classList.toggle('d-none', !tienePendientes); +} + +window.actualizarBadgeNotificaciones = actualizarBadgeNotificaciones; +actualizarBadgeNotificaciones(); + +document.addEventListener('DOMContentLoaded', function () { + actualizarBadgeNotificaciones(); +}); + +document.addEventListener('DOMContentLoaded', function () { + const soloNumeros = ['cuil', 'celular', 'telefono']; + const soloLetras = ['nombre', 'apellido']; + + const normalizarIdentificador = (valor) => String(valor || '').trim().toLowerCase(); + + const esCampo = (input, listaCampos) => { + const nombre = normalizarIdentificador(input.name); + const id = normalizarIdentificador(input.id); + return listaCampos.includes(nombre) || listaCampos.includes(id); + }; + + const esNumerico = (char) => /\d/.test(char); + const esLetra = (char) => /[A-Za-zÁÉÍÓÚáéíóúÑñ\s]/.test(char); + + const inputs = Array.from(document.querySelectorAll('input')); + + inputs.forEach((input) => { + if (esCampo(input, soloNumeros)) { + input.setAttribute('inputmode', 'numeric'); + input.setAttribute('pattern', '[0-9]+'); + + input.addEventListener('beforeinput', function (e) { + if (!esNumerico(e.data) && e.data !== null) { + e.preventDefault(); + } + }); + + input.addEventListener('input', function () { + input.value = input.value.replace(/\D/g, ''); + }); + } + + if (esCampo(input, soloLetras)) { + input.setAttribute('pattern', '[A-Za-zÁÉÍÓÚáéíóúÑñ\\s]+'); + + input.addEventListener('beforeinput', function (e) { + if (!esLetra(e.data) && e.data !== null) { + e.preventDefault(); + } + }); + + input.addEventListener('input', function () { + input.value = input.value.replace(/[^A-Za-zÁÉÍÓÚáéíóúÑñ\s]/g, ''); + }); + } + + if (input.type === 'password') { + const formAction = String(input.form?.getAttribute('action') || ''); + const nombre = normalizarIdentificador(input.name); + const esLogin = formAction.includes('/login/'); + const esContraActual = nombre === 'contra_actual' || nombre === 'contra_actual_secreta'; + + if (!esLogin && !esContraActual) { + input.setAttribute('minlength', '6'); + } + } + }); +}); + +document.addEventListener('DOMContentLoaded', function () { + if (!window.location.pathname.startsWith('/administrador')) { + return; + } + + const header = document.querySelector('.app-navbar'); + if (!header) { + limpiarEstadoPendingSidebar(); + return; + } + + const navbarContainer = header.querySelector('.navbar .container'); + const collapse = header.querySelector('#menuPrincipal'); + const logout = (collapse || header).querySelector('a[href="/logout"]'); + + const adminMenuList = (collapse || header).querySelector('ul.navbar-nav'); + if (adminMenuList && !adminMenuList.querySelector('a[href="/administrador/ayuda"]')) { + const ayudaItem = document.createElement('li'); + ayudaItem.className = 'nav-item'; + const ayudaLink = document.createElement('a'); + ayudaLink.className = 'btn app-navbar-link'; + ayudaLink.setAttribute('href', '/administrador/ayuda'); + ayudaLink.setAttribute('title', 'Ayuda para administrador'); + ayudaLink.setAttribute('aria-label', 'Ayuda para administrador'); + ayudaLink.textContent = '¿Necesitas Ayuda?'; + ayudaItem.appendChild(ayudaLink); + adminMenuList.appendChild(ayudaItem); + } + + const menuLinks = Array.from((collapse || header).querySelectorAll('ul .app-navbar-link')); + + if (menuLinks.length && !document.querySelector('.admin-sidebar')) { + const aside = document.createElement('aside'); + aside.className = 'admin-sidebar'; + aside.setAttribute('aria-label', 'Menu administrador'); + + const nav = document.createElement('nav'); + nav.className = 'admin-sidebar-nav'; + + const subitemsContenido = [ + { label: 'Quienes Somos', href: '/administrador/contenido/quienes-somos' }, + { label: 'Profesiones', href: '/administrador/contenido/profesiones' }, + { label: 'Servicios', href: '/administrador/contenido/servicios' }, + { label: 'Asistente Virtual', href: '/administrador/contenido/asistente-virtual/ver-faqs' }, + ]; + + menuLinks.forEach((link) => { + const href = link.getAttribute('href') || ''; + + if (href === '/administrador/contenido-web') { + const enContenido = + window.location.pathname.startsWith('/administrador/contenido/') || + window.location.pathname === '/administrador/contenido-web' || + window.location.pathname === '/administrador/asistente-consultas'; + + const details = document.createElement('details'); + details.className = 'admin-sidebar-details'; + if (enContenido) { + details.setAttribute('open', ''); + } + + const summary = document.createElement('summary'); + summary.className = 'btn app-navbar-link admin-sidebar-link admin-sidebar-summary'; + if (enContenido) { + summary.classList.add('active'); + } + summary.textContent = 'Contenido'; + details.appendChild(summary); + + const subitemsContenidoConConsultas = [ + ...subitemsContenido, + { label: 'Consultas sin respuesta', href: '/administrador/asistente-consultas' }, + ]; + + subitemsContenidoConConsultas.forEach(({ label, href: subhref }) => { + const a = document.createElement('a'); + a.className = 'btn app-navbar-link admin-sidebar-link admin-sidebar-sublink'; + a.setAttribute('href', subhref); + if (window.location.pathname === subhref) { + a.classList.add('active'); + } + a.textContent = label; + details.appendChild(a); + }); + + nav.appendChild(details); + return; + } + + const item = link.cloneNode(true); + item.classList.add('admin-sidebar-link'); + + const esRutaActiva = + href && + href !== '#top' && + (window.location.pathname === href || window.location.pathname.startsWith(`${href}/`)); + const esMisDatosEnDashboard = href === '#top' && window.location.pathname === '/administrador/dashboard'; + + if (esRutaActiva || esMisDatosEnDashboard) { + item.classList.add('active'); + } + + nav.appendChild(item); + }); + + aside.appendChild(nav); + header.insertAdjacentElement('afterend', aside); + } + + if (collapse) { + const ul = collapse.querySelector('ul'); + if (ul) { + ul.remove(); + } + + collapse.classList.remove('collapse', 'navbar-collapse'); + collapse.removeAttribute('id'); + collapse.classList.add('d-flex', 'ms-auto', 'align-items-center'); + } + + const toggler = header.querySelector('.navbar-toggler'); + if (toggler) { + toggler.remove(); + } + + if (logout && navbarContainer) { + logout.classList.add('btn', 'app-navbar-link'); + navbarContainer.appendChild(logout); + } + + const actualizarOffsetSidebar = () => { + const alturaNavbar = Math.ceil(header.offsetHeight || 74); + document.documentElement.style.setProperty('--admin-navbar-height', `${alturaNavbar}px`); + }; + + actualizarOffsetSidebar(); + window.addEventListener('resize', actualizarOffsetSidebar); + + limpiarEstadoPendingSidebar(); +}); + +document.addEventListener('DOMContentLoaded', function () { + if (!window.location.pathname.startsWith('/profesional')) { + return; + } + + const header = document.querySelector('.app-navbar'); + if (!header) { + limpiarEstadoPendingSidebar(); + return; + } + + const navbarContainer = header.querySelector('.navbar .container'); + const collapse = header.querySelector('#menuPrincipal'); + const logout = (collapse || header).querySelector('a[href="/logout"]'); + const menuList = (collapse || header).querySelector('ul.navbar-nav'); + if (menuList && !menuList.querySelector('a[href="/profesional/ayuda"]')) { + const ayudaItem = document.createElement('li'); + ayudaItem.className = 'nav-item'; + + const ayudaLink = document.createElement('a'); + ayudaLink.className = 'btn app-navbar-link'; + ayudaLink.setAttribute('href', '/profesional/ayuda'); + ayudaLink.setAttribute('title', 'Ayuda para profesionales'); + ayudaLink.setAttribute('aria-label', 'Ayuda para profesionales'); + ayudaLink.textContent = '¿Necesitas Ayuda?'; + + ayudaItem.appendChild(ayudaLink); + menuList.appendChild(ayudaItem); + } + + const menuLinks = Array.from((collapse || header).querySelectorAll('ul .app-navbar-link')); + + if (menuLinks.length && !document.querySelector('.admin-sidebar')) { + const aside = document.createElement('aside'); + aside.className = 'admin-sidebar'; + aside.setAttribute('aria-label', 'Menu profesional'); + + const nav = document.createElement('nav'); + nav.className = 'admin-sidebar-nav'; + + menuLinks.forEach((link) => { + const href = link.getAttribute('href') || ''; + + const item = link.cloneNode(true); + item.classList.add('admin-sidebar-link'); + + const esRutaActiva = + href && + href !== '#top' && + (window.location.pathname === href || window.location.pathname.startsWith(`${href}/`)); + const esMiAgendaEnDashboard = href === '#top' && window.location.pathname === '/profesional/dashboard'; + + if (esRutaActiva || esMiAgendaEnDashboard) { + item.classList.add('active'); + } + + nav.appendChild(item); + }); + + aside.appendChild(nav); + header.insertAdjacentElement('afterend', aside); + } + + if (collapse) { + const ul = collapse.querySelector('ul'); + if (ul) { + ul.remove(); + } + + collapse.classList.remove('collapse', 'navbar-collapse'); + collapse.removeAttribute('id'); + collapse.classList.add('d-flex', 'ms-auto', 'align-items-center'); + } + + const toggler = header.querySelector('.navbar-toggler'); + if (toggler) { + toggler.remove(); + } + + if (logout && navbarContainer) { + logout.classList.add('btn', 'app-navbar-link'); + navbarContainer.appendChild(logout); + } + + const actualizarOffsetSidebar = () => { + const alturaNavbar = Math.ceil(header.offsetHeight || 74); + document.documentElement.style.setProperty('--admin-navbar-height', `${alturaNavbar}px`); + }; + + actualizarOffsetSidebar(); + window.addEventListener('resize', actualizarOffsetSidebar); + + limpiarEstadoPendingSidebar(); +});