From aecaef1509b661e8404c8fb39bac7dcf936bfd66 Mon Sep 17 00:00:00 2001 From: LukiBeg Date: Fri, 7 Nov 2025 17:07:56 -0300 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20Cria=20modal-formul=C3=A1rio=20pa?= =?UTF-8?q?ra=20adi=C3=A7=C3=A3o=20de=20clientes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/AddClientController.php | 23 -- app/Http/Controllers/LogoutController.php | 2 +- app/Livewire/Admin/AddClient.php | 76 +++++ app/Livewire/Admin/ClientForm.php | 85 +++++ app/Services/ClientService.php | 4 +- composer.lock | 290 ++++++++---------- resources/css/app.css | 55 +++- resources/views/dashboard.blade.php | 8 +- .../views/livewire/admin/add-client.blade.php | 169 ++++++++++ resources/views/login.blade.php | 49 +-- routes/web.php | 8 +- 11 files changed, 538 insertions(+), 231 deletions(-) delete mode 100644 app/Http/Controllers/AddClientController.php create mode 100644 app/Livewire/Admin/AddClient.php create mode 100644 app/Livewire/Admin/ClientForm.php create mode 100644 resources/views/livewire/admin/add-client.blade.php diff --git a/app/Http/Controllers/AddClientController.php b/app/Http/Controllers/AddClientController.php deleted file mode 100644 index 03eb091..0000000 --- a/app/Http/Controllers/AddClientController.php +++ /dev/null @@ -1,23 +0,0 @@ -clientService = $clientService; - } - - - public function addClient(Request $request) - { - - dd($this->clientService); - } -} diff --git a/app/Http/Controllers/LogoutController.php b/app/Http/Controllers/LogoutController.php index 903f193..ee8bbd0 100644 --- a/app/Http/Controllers/LogoutController.php +++ b/app/Http/Controllers/LogoutController.php @@ -15,6 +15,6 @@ public function logout(Request $request) $request->session()->invalidate(); $request->session()->regenerateToken(); - return redirect('/')->with('success', 'Logout efetuado com sucesso!'); + return redirect('/login')->with('success', 'Logout efetuado com sucesso!'); } } diff --git a/app/Livewire/Admin/AddClient.php b/app/Livewire/Admin/AddClient.php new file mode 100644 index 0000000..ebb3bf5 --- /dev/null +++ b/app/Livewire/Admin/AddClient.php @@ -0,0 +1,76 @@ +form->reset(); // Limpa o formulário de dados antigos + $this->showModal = true; + } + + /** + * Fecha e limpa o modal. + */ + public function closeModal() + { + $this->showModal = false; + $this->form->reset(); + } + + /** + * Método principal chamado pelo wire:submit="save". + */ + public function save() + { + // 3. Valida os dados usando as 'rules' do ClientForm.php + $this->form->validate(); + + // 4. Pega todos os dados validados + $data = $this->form->all(); + + // 5. Lida com o upload do arquivo de imagem + if ($this->form->profile_image_path) { + $path = $this->form->profile_image_path->store('client_logos', 'public'); + $data['profile_image_path'] = $path; + } + + // 6. Cria o cliente no banco de dados + Client::create($data); + + // 7. Fecha o modal + $this->closeModal(); + + // 8. Despacha um evento para atualizar outros componentes (ex: o grid de clientes) + $this->dispatch('clientAdded'); + + // (Opcional) Envia uma notificação de sucesso + // $this->dispatch('notify', 'Cliente adicionado com sucesso!'); + } + + /** + * Renderiza a view do modal. + */ + public function render() + { + return view('livewire.admin.add-client'); + } +} \ No newline at end of file diff --git a/app/Livewire/Admin/ClientForm.php b/app/Livewire/Admin/ClientForm.php new file mode 100644 index 0000000..f4ceabb --- /dev/null +++ b/app/Livewire/Admin/ClientForm.php @@ -0,0 +1,85 @@ +toArray(); + $data['has_call_center'] = (bool) $client->has_call_center; + $data['has_voice_gateway'] = (bool) $client->has_voice_gateway; + $data['has_fop2'] = (bool) $client->has_fop2; + + $this->fill($data); + } + + // 4. ADICIONADO: Método de Regras + /** + * Define as regras de validação para o formulário. + */ + public function rules() + { + return [ + 'name' => 'required|string|max:255', + 'legal_name' => 'nullable|string|max:255', + 'cnpj' => 'nullable|string|max:20', + 'profile_image_path' => 'nullable|image|max:2048', // 2MB Max + 'pbx_hosting' => 'nullable|string|max:255', + 'activation_date' => 'nullable|date', + 'carrier' => 'nullable|string|max:255', + 'access_type' => 'nullable|string|max:255', + 'server_ip' => 'nullable|ip', + 'root_password' => 'nullable|string', + 'has_call_center' => 'boolean', + 'has_voice_gateway' => 'boolean', + 'has_fop2' => 'boolean', + 'modules' => 'nullable|json', + 'whatsapp_number' => 'nullable|string|max:20', + 'whatsapp_activation_date' => 'nullable|date', + ]; + } + + // 5. ADICIONADO: Método de Mensagens Customizadas + /** + * Define as mensagens de erro customizadas. + */ + public function messages() + { + return [ + 'name.required' => 'O campo Nome Fantasia é obrigatório.', + 'name.max' => 'O Nome Fantasia não pode ter mais que 255 caracteres.', + + 'profile_image_path.image' => 'O arquivo deve ser uma imagem válida (jpg, png, etc.).', + 'profile_image_path.max' => 'A imagem não pode ser maior que 2MB.', + + 'server_ip.ip' => 'Por favor, insira um endereço de IP válido.', + 'modules.json' => 'O campo módulos deve conter um formato JSON válido.', + + '*.date' => 'Por favor, insira uma data válida.', + '*.boolean' => 'Este campo deve ser verdadeiro ou falso.', + ]; + } +} diff --git a/app/Services/ClientService.php b/app/Services/ClientService.php index 0437a5e..0b0cbd6 100644 --- a/app/Services/ClientService.php +++ b/app/Services/ClientService.php @@ -10,5 +10,7 @@ class ClientService { public function __construct(protected Client $client) {} - public function addClient() {} + public function addClient($client) { + return Client::create($client); + } } diff --git a/composer.lock b/composer.lock index b7c2ce2..c01ed3c 100644 --- a/composer.lock +++ b/composer.lock @@ -379,29 +379,28 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.4.0", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "8c784d071debd117328803d86b2097615b457500" + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", - "reference": "8c784d071debd117328803d86b2097615b457500", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", "shasum": "" }, "require": { - "php": "^7.2|^8.0", - "webmozart/assert": "^1.0" + "php": "^8.2|^8.3|^8.4|^8.5" }, "replace": { "mtdowling/cron-expression": "^1.0" }, "require-dev": { - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" }, "type": "library", "extra": { @@ -432,7 +431,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" }, "funding": [ { @@ -440,7 +439,7 @@ "type": "github" } ], - "time": "2024-10-09T13:47:03+00:00" + "time": "2025-10-31T18:51:33+00:00" }, { "name": "egulias/email-validator", @@ -1055,16 +1054,16 @@ }, { "name": "laravel/framework", - "version": "v12.35.1", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "d6d6e3cb68238e2fb25b440f222442adef5a8a15" + "reference": "3c3c4ad30f5b528b164a7c09aa4ad03118c4c125" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d6d6e3cb68238e2fb25b440f222442adef5a8a15", - "reference": "d6d6e3cb68238e2fb25b440f222442adef5a8a15", + "url": "https://api.github.com/repos/laravel/framework/zipball/3c3c4ad30f5b528b164a7c09aa4ad03118c4c125", + "reference": "3c3c4ad30f5b528b164a7c09aa4ad03118c4c125", "shasum": "" }, "require": { @@ -1270,7 +1269,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-10-23T15:25:03+00:00" + "time": "2025-11-04T15:39:33+00:00" }, { "name": "laravel/prompts", @@ -2359,25 +2358,25 @@ }, { "name": "nette/schema", - "version": "v1.3.2", + "version": "v1.3.3", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "da801d52f0354f70a638673c4a0f04e16529431d" + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", - "reference": "da801d52f0354f70a638673c4a0f04e16529431d", + "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.4" + "php": "8.1 - 8.5" }, "require-dev": { "nette/tester": "^2.5.2", - "phpstan/phpstan-nette": "^1.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -2387,6 +2386,9 @@ } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -2415,9 +2417,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.2" + "source": "https://github.com/nette/schema/tree/v1.3.3" }, - "time": "2024-10-06T23:10:23+00:00" + "time": "2025-10-30T22:57:59+00:00" }, { "name": "nette/utils", @@ -3493,16 +3495,16 @@ }, { "name": "symfony/console", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", - "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", "shasum": "" }, "require": { @@ -3567,7 +3569,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.4" + "source": "https://github.com/symfony/console/tree/v7.3.6" }, "funding": [ { @@ -3587,20 +3589,20 @@ "type": "tidelift" } ], - "time": "2025-09-22T15:31:00+00:00" + "time": "2025-11-04T01:21:42+00:00" }, { "name": "symfony/css-selector", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" + "reference": "84321188c4754e64273b46b406081ad9b18e8614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/84321188c4754e64273b46b406081ad9b18e8614", + "reference": "84321188c4754e64273b46b406081ad9b18e8614", "shasum": "" }, "require": { @@ -3636,7 +3638,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.3.0" + "source": "https://github.com/symfony/css-selector/tree/v7.3.6" }, "funding": [ { @@ -3647,12 +3649,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-10-29T17:24:25+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3723,16 +3729,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4" + "reference": "bbe40bfab84323d99dab491b716ff142410a92a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", - "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/bbe40bfab84323d99dab491b716ff142410a92a8", + "reference": "bbe40bfab84323d99dab491b716ff142410a92a8", "shasum": "" }, "require": { @@ -3780,7 +3786,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.4" + "source": "https://github.com/symfony/error-handler/tree/v7.3.6" }, "funding": [ { @@ -3800,7 +3806,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-10-31T19:12:50+00:00" }, { "name": "symfony/event-dispatcher", @@ -3964,16 +3970,16 @@ }, { "name": "symfony/finder", - "version": "v7.3.2", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" + "reference": "9f696d2f1e340484b4683f7853b273abff94421f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", + "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", + "reference": "9f696d2f1e340484b4683f7853b273abff94421f", "shasum": "" }, "require": { @@ -4008,7 +4014,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.2" + "source": "https://github.com/symfony/finder/tree/v7.3.5" }, "funding": [ { @@ -4028,20 +4034,20 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2025-10-15T18:45:57+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6" + "reference": "6379e490d6ecfc5c4224ff3a754b90495ecd135c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c061c7c18918b1b64268771aad04b40be41dd2e6", - "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6379e490d6ecfc5c4224ff3a754b90495ecd135c", + "reference": "6379e490d6ecfc5c4224ff3a754b90495ecd135c", "shasum": "" }, "require": { @@ -4091,7 +4097,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.4" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.6" }, "funding": [ { @@ -4111,20 +4117,20 @@ "type": "tidelift" } ], - "time": "2025-09-16T08:38:17+00:00" + "time": "2025-11-06T11:05:57+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b796dffea7821f035047235e076b60ca2446e3cf" + "reference": "f9a34dc0196677250e3609c2fac9de9e1551a262" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b796dffea7821f035047235e076b60ca2446e3cf", - "reference": "b796dffea7821f035047235e076b60ca2446e3cf", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9a34dc0196677250e3609c2fac9de9e1551a262", + "reference": "f9a34dc0196677250e3609c2fac9de9e1551a262", "shasum": "" }, "require": { @@ -4209,7 +4215,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.4" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.6" }, "funding": [ { @@ -4229,20 +4235,20 @@ "type": "tidelift" } ], - "time": "2025-09-27T12:32:17+00:00" + "time": "2025-11-06T20:58:12+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "ab97ef2f7acf0216955f5845484235113047a31d" + "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/ab97ef2f7acf0216955f5845484235113047a31d", - "reference": "ab97ef2f7acf0216955f5845484235113047a31d", + "url": "https://api.github.com/repos/symfony/mailer/zipball/fd497c45ba9c10c37864e19466b090dcb60a50ba", + "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba", "shasum": "" }, "require": { @@ -4293,7 +4299,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.4" + "source": "https://github.com/symfony/mailer/tree/v7.3.5" }, "funding": [ { @@ -4313,7 +4319,7 @@ "type": "tidelift" } ], - "time": "2025-09-17T05:51:54+00:00" + "time": "2025-10-24T14:27:20+00:00" }, { "name": "symfony/mime", @@ -5299,16 +5305,16 @@ }, { "name": "symfony/routing", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c" + "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8dc648e159e9bac02b703b9fbd937f19ba13d07c", - "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c", + "url": "https://api.github.com/repos/symfony/routing/zipball/c97abe725f2a1a858deca629a6488c8fc20c3091", + "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091", "shasum": "" }, "require": { @@ -5360,7 +5366,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.4" + "source": "https://github.com/symfony/routing/tree/v7.3.6" }, "funding": [ { @@ -5380,20 +5386,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-11-05T07:57:47+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -5447,7 +5453,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -5458,12 +5464,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", @@ -5657,16 +5667,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { @@ -5715,7 +5725,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { @@ -5726,12 +5736,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-27T08:32:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/uid", @@ -5809,16 +5823,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb" + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", - "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d", + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d", "shasum": "" }, "require": { @@ -5872,7 +5886,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.5" }, "funding": [ { @@ -5892,7 +5906,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -6106,64 +6120,6 @@ } ], "time": "2024-11-21T01:49:47+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.12.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "541057574806f942c94662b817a50f63f7345360" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/541057574806f942c94662b817a50f63f7345360", - "reference": "541057574806f942c94662b817a50f63f7345360", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-date": "*", - "ext-filter": "*", - "php": "^7.2 || ^8.0" - }, - "suggest": { - "ext-intl": "", - "ext-simplexml": "", - "ext-spl": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.12.0" - }, - "time": "2025-10-20T12:43:39+00:00" } ], "packages-dev": [ @@ -6499,16 +6455,16 @@ }, { "name": "laravel/sail", - "version": "v1.46.0", + "version": "v1.47.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "eb90c4f113c4a9637b8fdd16e24cfc64f2b0ae6e" + "reference": "9a11e822238167ad8b791e4ea51155d25cf4d8f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/eb90c4f113c4a9637b8fdd16e24cfc64f2b0ae6e", - "reference": "eb90c4f113c4a9637b8fdd16e24cfc64f2b0ae6e", + "url": "https://api.github.com/repos/laravel/sail/zipball/9a11e822238167ad8b791e4ea51155d25cf4d8f2", + "reference": "9a11e822238167ad8b791e4ea51155d25cf4d8f2", "shasum": "" }, "require": { @@ -6521,7 +6477,7 @@ }, "require-dev": { "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^2.0" }, "bin": [ "bin/sail" @@ -6558,7 +6514,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2025-09-23T13:44:39+00:00" + "time": "2025-10-28T13:55:29+00:00" }, { "name": "mockery/mockery", @@ -7257,16 +7213,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.42", + "version": "11.5.43", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c" + "reference": "c6b89b6cf4324a8b4cb86e1f5dfdd6c9e0371924" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c", - "reference": "1c6cb5dfe412af3d0dfd414cfd110e3b9cfdbc3c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c6b89b6cf4324a8b4cb86e1f5dfdd6c9e0371924", + "reference": "c6b89b6cf4324a8b4cb86e1f5dfdd6c9e0371924", "shasum": "" }, "require": { @@ -7338,7 +7294,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.42" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.43" }, "funding": [ { @@ -7362,7 +7318,7 @@ "type": "tidelift" } ], - "time": "2025-09-28T12:09:13+00:00" + "time": "2025-10-30T08:39:39+00:00" }, { "name": "sebastian/cli-parser", @@ -8404,16 +8360,16 @@ }, { "name": "symfony/yaml", - "version": "v7.3.3", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "d4f4a66866fe2451f61296924767280ab5732d9d" + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d", - "reference": "d4f4a66866fe2451f61296924767280ab5732d9d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/90208e2fc6f68f613eae7ca25a2458a931b1bacc", + "reference": "90208e2fc6f68f613eae7ca25a2458a931b1bacc", "shasum": "" }, "require": { @@ -8456,7 +8412,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.3" + "source": "https://github.com/symfony/yaml/tree/v7.3.5" }, "funding": [ { @@ -8476,7 +8432,7 @@ "type": "tidelift" } ], - "time": "2025-08-27T11:34:33+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "theseer/tokenizer", @@ -8538,5 +8494,5 @@ "php": "^8.2" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/resources/css/app.css b/resources/css/app.css index f7cb2c4..d441f1d 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -10,9 +10,29 @@ @theme { 'Segoe UI Symbol', 'Noto Color Emoji'; } +@layer utilities { + .forget-password>a { + color: black !important; + } + + .navbar-items>a { + color: black !important; + } +} @layer components { + /* Estilos globais */ + h1 { + @apply text-black font-semibold; + @apply border-b border-blue-500 rounded-md shadow-md shadow-blue-400; + @apply p-4 transition-all duration-300 transform hover:scale-105; + } + + * {} + + /* Fim estilos globais */ + /* body */ body { @apply bg-white h-screen w-screen; @@ -26,17 +46,13 @@ @layer components { /* Header - Navbar */ .nav-bar { @apply flex flex-nowrap justify-center items-center mx-auto px-4 sm:px-6 lg:px-8 h-10; - @apply relative; + + /* REMOVIDO: @apply relative; */ + + /* 1. ADICIONADO: 'top-0 left-0' para "colar" no topo */ + @apply fixed top-0 left-0 z-50 w-full; @apply bg-transparent text-black; - @apply fixed z-50 w-full; - - /* 1. ADICIONADO: A borda agora mora aqui */ - @apply border-b; - - /* 2. ESTADO BASE (Topo): A borda começa transparente */ - @apply border-transparent; - - /* 3. ADICIONADO: Também transiciona a cor da borda */ + @apply border-b border-transparent; @apply transition-colors duration-700 ease-in-out; } @@ -157,7 +173,16 @@ @layer components { .container { /* Deixe APENAS as classes de largura, margem e padding */ - @apply w-full mx-auto px-4 sm:px-6 lg:px-8 mt-10 mb-10; + @apply w-full mx-auto px-4 sm:px-6 lg:px-8 mt-15 mb-10; + } + + .container-form { + @apply w-full h-full mx-auto; + @apply flex justify-center items-center; + } + + .messages { + @apply flex justify-center; } .form-class { @@ -173,6 +198,12 @@ @layer components { @apply bg-blue-500 text-white rounded-md p-2 cursor-pointer hover:bg-blue-800 w-1/2; } + .forget-password { + @apply flex justify-center w-1/2; + @apply shadow-lg rounded-md; + @apply transition scale-80 hover:scale-95; + } + .container h1 { @apply text-2xl font-bold transition-all duration-300 transform hover:scale-105 mb-4; } @@ -182,7 +213,7 @@ @layer components { .client-card { /* Layout: Flex-col (vertical) */ @apply flex flex-col justify-between; - @apply hover:transition duration-150 ease-in-out hover:scale-120; + @apply hover:transition duration-150 ease-in-out hover:scale-110; /* Estilo: Sombra, borda, fundo branco */ @apply bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 ease-in-out; diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 3ceabc0..aa99116 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -1,11 +1,9 @@ @extends('layouts.app') @section('content') - -

Dashboard

- +
@@ -386,6 +384,10 @@ class="w-32 h-32 rounded-full object-cover">
+ + + + @endsection \ No newline at end of file diff --git a/resources/views/livewire/admin/add-client.blade.php b/resources/views/livewire/admin/add-client.blade.php new file mode 100644 index 0000000..8071661 --- /dev/null +++ b/resources/views/livewire/admin/add-client.blade.php @@ -0,0 +1,169 @@ +
+ +
+ +
+ +
+ +
+
+

+ Adicionar Novo Cliente +

+ +
+
+ +
+
+ +
+ +
+

Informações Principais

+ +
+
+ + + @error('form.name') {{ $message }} @enderror +
+
+ + + @error('form.legal_name') {{ $message }} @enderror +
+
+
+ + + @error('form.cnpj') {{ $message }} @enderror +
+
+ + + +
+ Enviando... +
+ @error('form.profile_image_path') {{ $message }} @enderror +
+
+ +
+ +
+

Detalhes do Servidor PBX

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + @error('form.server_ip') {{ $message }} @enderror +
+
+ + +
+
+
+ +
+ +
+

WhatsApp

+
+
+ + +
+
+ + +
+
+
+
+
+
+

Módulos & Recursos

+
+ + + +
+
+ + + @error('form.modules') {{ $message }} @enderror +
+
+
+
+
+ + + + +
+
+ +
+
+
+
\ No newline at end of file diff --git a/resources/views/login.blade.php b/resources/views/login.blade.php index 5db54be..6bdba26 100644 --- a/resources/views/login.blade.php +++ b/resources/views/login.blade.php @@ -2,36 +2,47 @@ @section('title', 'Login') @section('content') -
-
+
+
+
+ +
+ +
+

Efetue seu login

-

Efetue seu login

- @if ($errors->any()) +
+ @if ($errors->any())
{{ $errors->first() }}
- @endif + @endif - @if (session('error')) + @if (session('error'))
{{ session('error') }}
- @endif - - - @csrf - - -
- -
- -
- Esqueceu sua senha? + @endif
-
+ + @csrf + + +
+ +
+ + + + + +
@endsection \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index ad9e16c..8392538 100644 --- a/routes/web.php +++ b/routes/web.php @@ -26,11 +26,9 @@ Route::post('/logout', [LogoutController::class, 'logout'])->name('logout'); }); - Route::get('/create-users', function () { - return view('users.create-users'); - })->name('users.create')->middleware('authorization'); - - Route::get('/add-client', [AddClientController::class, 'addClient']); + // Route::get('/add-client', function () { + // return view('clients.add-client'); + // })->name('clients.add')->middleware('authorization'); }); Route::controller(LoginController::class)->group(function () { From dcb9d796d1c917b1909904f4461d4af618591de9 Mon Sep 17 00:00:00 2001 From: lukibeg Date: Sat, 8 Nov 2025 19:50:38 -0300 Subject: [PATCH 02/12] refactor: refatora app.css o dividindo em arquivos menores. --- resources/css/app.css | 194 +--------------- resources/css/client-cards.css | 69 ++++++ resources/css/client-form.css | 375 ++++++++++++++++++++++++++++++ resources/css/header.css | 122 ++++++++++ resources/css/toast.css | 149 ++++++++++++ resources/css/user-form-modal.css | 157 +++++++++++++ 6 files changed, 879 insertions(+), 187 deletions(-) create mode 100644 resources/css/client-cards.css create mode 100644 resources/css/client-form.css create mode 100644 resources/css/header.css create mode 100644 resources/css/toast.css create mode 100644 resources/css/user-form-modal.css diff --git a/resources/css/app.css b/resources/css/app.css index d441f1d..2afe698 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -5,6 +5,12 @@ @source '../**/*.blade.php'; @source '../**/*.js'; +@import './toast.css'; +@import './client-form.css'; +@import './user-form-modal.css'; +@import './header.css'; +@import './client-cards.css'; + @theme { --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; @@ -43,125 +49,6 @@ @layer components { /*End body */ - /* Header - Navbar */ - .nav-bar { - @apply flex flex-nowrap justify-center items-center mx-auto px-4 sm:px-6 lg:px-8 h-10; - - /* REMOVIDO: @apply relative; */ - - /* 1. ADICIONADO: 'top-0 left-0' para "colar" no topo */ - @apply fixed top-0 left-0 z-50 w-full; - @apply bg-transparent text-black; - @apply border-b border-transparent; - @apply transition-colors duration-700 ease-in-out; - } - - .nav-bar::before { - content: ''; - @apply absolute top-0 left-0 w-full h-full; - - /* 2. ESTADO BASE (Topo): Fundo e Sombra */ - @apply bg-white; - @apply shadow-md shadow-blue-400; - /* 4. REMOVIDO: As classes 'border-b border-white' saíram daqui */ - - /* 3. TRANSIÇÃO (Correto) */ - @apply transition-all duration-700 ease-in-out; - - @apply -z-10; - } - - - .navbar-scrolled { - @apply border-white shadow-md; - } - - .navbar-scrolled::before { - @apply opacity-0; - @apply shadow-none; - } - - .nav-bar>.navbar-items>a:hover, - .nav-bar>.navbar-items>a { - @apply transition-all duration-300 transform hover:scale-105; - @apply mr-7; - @apply hover:border hover:shadow-md shadow-blue-400 border-blue-300 rounded-md p-1 transition-all duration-250 transform hover:scale-105; - } - - .nav-bar-logo { - /* Garante que a largura não ultrapasse 100% */ - @apply rounded-md max-w-25 mr-10; - height: auto; - /* Altura proporcional à largura */ - } - - .navbar-items { - @apply flex flex-nowrap justify-center items-center px-4 sm:px-6 lg:px-8 h-10; - } - - .profile-menu { - @apply absolute right-5; - @apply rounded-xl; - @apply max-w-7 max-h-7; - } - - .profile-list-items { - /* 1. Posicionamento (Correto) */ - @apply absolute top-full right-0 ml-4 mt-2; - - @apply block w-full; - /* Adicionei mt-2 para descolar do ícone */ - - /* 2. Largura Fixa (Mais limpo) */ - @apply w-56; - - /* 3. Estilos do Contêiner (Profissional) */ - @apply bg-white p-2 rounded-md shadow-xl; - /* Sombra mais forte */ - @apply border border-gray-200; - - /* 4. Divisórias Sutis entre os
  • */ - @apply divide-y divide-gray-100; - } - - .profile-link { - /* Você pode aplicar isso direto no */ - /* 1. FAZ O LINK PREENCHER O
  • */ - @apply block w-full; - - } - - .profile-items { - /* 1. Limpeza (Classes antigas removidas) */ - - /* 2. Espaçamento Interno (Respiro) */ - @apply px-4 py-2; - - /* 3. Estilos de Texto */ - @apply text-sm text-gray-700; - - /* 4. Interatividade */ - @apply hover:bg-blue-100 cursor-pointer; - - /* 5. Transição Suave */ - @apply transition-colors duration-150 ease-in-out; - - /* 6. Cantos do Hover */ - @apply rounded-md; - } - - .nav-bar-logo img { - @apply w-full; - /* Largura 100% para se ajustar ao container */ - height: auto; - /* Mantém a proporção original da imagem */ - } - - .nav-bar>.navbar-items>form>button { - @apply hover:border hover:shadow-md hover:scale-105 hover:cursor-pointer shadow-blue-400 border-blue-300 rounded-md p-1 transition-all duration-250 transform; - } - - /*End Header - Navbar */ /* Container */ .container-title { @@ -206,71 +93,4 @@ @layer components { .container h1 { @apply text-2xl font-bold transition-all duration-300 transform hover:scale-105 mb-4; - } - - /* Estilos para o card do cliente */ - /* CONTAINER PRINCIPAL DO CARD */ - .client-card { - /* Layout: Flex-col (vertical) */ - @apply flex flex-col justify-between; - @apply hover:transition duration-150 ease-in-out hover:scale-110; - - /* Estilo: Sombra, borda, fundo branco */ - @apply bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 ease-in-out; - @apply border border-gray-100; - - /* Garante que o conteúdo não vaze */ - @apply overflow-hidden; - } - - /* O "RETÂNGULO" SUPERIOR (Avatar + Opções) */ - .client-card-header { - /* 1. POSIÇÃO: 'relative' para ancorar o menu de opções */ - @apply relative; - - /* 2. LAYOUT: Centraliza o avatar */ - @apply flex justify-center items-center; - - /* 3. ESPAÇAMENTO: Padding interno */ - @apply p-6; - - /* 4. (Opcional) Fundo sutil para o retângulo */ - @apply bg-gray-50; - } - - /* AVATAR DO CLIENTE */ - .client-avatar { - /* A imagem interna já tem as classes de tamanho (w-32 h-32) */ - } - - /* MENU DE OPÇÕES (Três Pontinhos) */ - .client-options-menu { - /* 1. POSIÇÃO: Flutua sobre o header */ - @apply absolute top-4 right-4; - } - - .client-options-button { - @apply p-2 rounded-full hover:bg-gray-200 transition-colors; - @apply focus:outline-none focus:ring-2 focus:ring-blue-500; - } - - /* LISTA DE OPÇÕES (Dropdown) */ - .client-options-list { - @apply absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg py-1 z-10; - @apply divide-y divide-gray-100; - } - - .client-option-item { - @apply block px-4 py-2 text-sm text-gray-700 hover:bg-blue-100 hover:text-blue-800 transition-colors; - @apply cursor-pointer; - } - - - /* NOME DO CLIENTE (Área inferior) */ - .client-card-name { - @apply text-lg font-semibold text-gray-800 text-center; - - /* Espaçamento e uma borda sutil no topo */ - @apply p-4 border-t border-gray-100; - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/resources/css/client-cards.css b/resources/css/client-cards.css new file mode 100644 index 0000000..734ddaf --- /dev/null +++ b/resources/css/client-cards.css @@ -0,0 +1,69 @@ +@layer components { + + /* Estilos para o card do cliente */ + /* CONTAINER PRINCIPAL DO CARD */ + .client-card { + /* Layout: Flex-col (vertical) */ + @apply flex flex-col justify-between; + @apply hover:transition duration-150 ease-in-out hover:scale-110; + + /* Estilo: Sombra, borda, fundo branco */ + @apply bg-white rounded-lg shadow-md hover:shadow-xl transition-shadow duration-300 ease-in-out; + @apply border border-gray-100; + + /* Garante que o conteúdo não vaze */ + @apply overflow-hidden; + } + + /* O "RETÂNGULO" SUPERIOR (Avatar + Opções) */ + .client-card-header { + /* 1. POSIÇÃO: 'relative' para ancorar o menu de opções */ + @apply relative; + + /* 2. LAYOUT: Centraliza o avatar */ + @apply flex justify-center items-center; + + /* 3. ESPAÇAMENTO: Padding interno */ + @apply p-6; + + /* 4. (Opcional) Fundo sutil para o retângulo */ + @apply bg-gray-50; + } + + /* AVATAR DO CLIENTE */ + .client-avatar { + /* A imagem interna já tem as classes de tamanho (w-32 h-32) */ + } + + /* MENU DE OPÇÕES (Três Pontinhos) */ + .client-options-menu { + /* 1. POSIÇÃO: Flutua sobre o header */ + @apply absolute top-4 right-4; + } + + .client-options-button { + @apply p-2 rounded-full hover:bg-gray-200 transition-colors; + @apply focus:outline-none focus:ring-2 focus:ring-blue-500; + } + + /* LISTA DE OPÇÕES (Dropdown) */ + .client-options-list { + @apply absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg py-1 z-10; + @apply divide-y divide-gray-100; + } + + .client-option-item { + @apply block px-4 py-2 text-sm text-gray-700 hover:bg-blue-100 hover:text-blue-800 transition-colors; + @apply cursor-pointer; + } + + + /* NOME DO CLIENTE (Área inferior) */ + .client-card-name { + @apply text-lg font-semibold text-gray-800 text-center; + + /* Espaçamento e uma borda sutil no topo */ + @apply p-4 border-t border-gray-100; + } +} +} \ No newline at end of file diff --git a/resources/css/client-form.css b/resources/css/client-form.css new file mode 100644 index 0000000..ca166b8 --- /dev/null +++ b/resources/css/client-form.css @@ -0,0 +1,375 @@ +@layer components { + + + /* ================================================================== */ + /* Estilos do Formulário do Modal (Slide-Over) - Versão Aprimorada */ + /* ================================================================== */ + + /* Define o layout de duas colunas com gap responsivo */ + .form-grid-container { + @apply lg:grid lg:grid-cols-3 lg:gap-8; + animation: fadeIn 0.3s ease-out; + } + + /* Coluna principal que ocupa 2/3 do espaço */ + .form-main-panel-modal { + @apply lg:col-span-2; + } + + /* Coluna da sidebar que ocupa 1/3 do espaço */ + .form-sidebar-column { + @apply lg:col-span-1; + } + + /* Faz a sidebar "grudar" no topo ao rolar a página com transição suave */ + .form-sticky-sidebar-modal { + @apply lg:sticky lg:top-6; + transition: top 0.2s ease-out; + } + + /* Wrapper principal dentro do modal com padding otimizado */ + .form-wrapper-modal { + @apply flex-1 px-4 py-6 sm:px-6; + } + + /* Bloco de um grupo de campos com espaçamento melhorado */ + .form-group { + @apply py-5; + animation: slideInUp 0.4s ease-out; + } + + /* Título de uma seção com melhor hierarquia visual */ + .form-section-title { + @apply text-base font-semibold leading-6 text-gray-900 text-gray-100; + letter-spacing: -0.01em; + } + + /* Divisor com gradiente azul-roxo inspirado na imagem */ + .form-divider { + @apply my-4; + background: linear-gradient(to right, transparent, #5bb5f0, #a8a3e8, transparent); + height: 1px; + border: none; + opacity: 0.3; + } + + /* Card com borda azul suave */ + .form-section-card { + @apply rounded-lg p-4; + border: 1px solid #e3f2fd; + background: linear-gradient(135deg, #f8fbff 0%, #f5f8ff 100%); + backdrop-filter: blur(8px); + transition: all 0.2s ease; + } + + .form-section-card:hover { + border-color: #bbdefb; + box-shadow: 0 2px 8px rgba(91, 181, 240, 0.1); + } + + /* Dark mode para card */ + @media (prefers-color-scheme: dark) { + .form-section-card { + background: linear-gradient(135deg, rgba(91, 181, 240, 0.05) 0%, rgba(168, 163, 232, 0.05) 100%); + border-color: rgba(91, 181, 240, 0.2); + } + + .form-section-card:hover { + border-color: rgba(91, 181, 240, 0.3); + } + } + + /* ================================================================== */ + /* Estilos dos Campos de Formulário */ + /* ================================================================== */ + + /* Rótulo (Label) padrão dos campos com melhor contraste */ + .form-label { + /* Ajustado para texto mais escuro e melhor contraste */ + @apply block text-sm font-medium leading-6 text-gray-700 text-gray-300 mb-2; + transition: color 0.15s ease; + } + + /* Input com cores do tema azul-roxo */ + .form-input { + /* Campos agora são brancos/claros com texto escuro para melhor contraste */ + @apply block w-full rounded-md border-0 py-2 px-3 text-gray-900 text-gray-400 shadow-sm placeholder:text-gray-400 placeholder:text-gray-500 sm:text-sm sm:leading-6; + background-color: #ffffff; + ring-color: #bbdefb; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + } + + input[type="date"] { + outline: none; + border: none; + color: black; + } + + .form-input:focus { + outline: none; + ring-width: 2px; + ring-color: #5bb5f0; + box-shadow: 0 0 0 3px rgba(91, 181, 240, 0.15); + } + + .form-input:hover:not(:focus) { + ring-color: #90caf9; + } + + @media (prefers-color-scheme: dark) { + .form-input { + background-color: rgba(255, 255, 255, 0.05); + ring-color: rgba(91, 181, 240, 0.3); + } + + .form-input:focus { + ring-color: #5bb5f0; + } + + .form-input:hover:not(:focus) { + ring-color: rgba(91, 181, 240, 0.4); + } + } + + /* Input de arquivo com tema azul-roxo */ + .form-file-input { + @apply block w-full text-sm text-gray-900 rounded-lg cursor-pointer bg-gray-50; + border: 1px solid #bbdefb; + transition: all 0.2s ease; + } + + .form-file-input::file-selector-button { + margin-right: 1rem; + padding: 0.5rem 1rem; + border: none; + border-radius: 0.5rem 0 0 0.5rem; + font-size: 0.875rem; + font-weight: 600; + background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%); + color: #1976d2; + transition: all 0.2s ease; + } + + .form-file-input:hover::file-selector-button { + background: linear-gradient(135deg, #bbdefb 0%, #e1bee7 100%); + } + + .form-file-input:focus { + outline: none; + border-color: #5bb5f0; + box-shadow: 0 0 0 2px rgba(91, 181, 240, 0.2); + } + + @media (prefers-color-scheme: dark) { + .form-file-input { + border-color: rgba(91, 181, 240, 0.3); + } + + .form-file-input::file-selector-button { + background: linear-gradient(135deg, rgba(91, 181, 240, 0.15) 0%, rgba(168, 163, 232, 0.15) 100%); + color: #90caf9; + } + + .form-file-input:hover::file-selector-button { + background: linear-gradient(135deg, rgba(91, 181, 240, 0.25) 0%, rgba(168, 163, 232, 0.25) 100%); + } + } + + /* Grupo de checkboxes com melhor espaçamento */ + .form-checkbox-group { + @apply space-y-3 mt-2; + } + + /* Item individual do checkbox com hover suave */ + .form-checkbox-item { + @apply flex items-center gap-x-3 p-2 rounded-md; + transition: background-color 0.15s ease; + } + + .form-checkbox-item:hover { + background-color: rgba(91, 181, 240, 0.05); + } + + @media (prefers-color-scheme: dark) { + .form-checkbox-item:hover { + background-color: rgba(91, 181, 240, 0.1); + } + } + + /* Checkbox com cores azul-roxo */ + .form-checkbox { + @apply h-4 w-4 rounded cursor-pointer; + border: 2px solid #90caf9; + transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); + accent-color: #5bb5f0; + } + + .form-checkbox:checked { + background-color: #5bb5f0; + border-color: #5bb5f0; + animation: checkboxPop 0.2s ease; + } + + .form-checkbox:hover { + border-color: #5bb5f0; + } + + .form-checkbox:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(91, 181, 240, 0.2); + } + + @media (prefers-color-scheme: dark) { + .form-checkbox { + border-color: rgba(91, 181, 240, 0.4); + } + } + + /* ================================================================== */ + /* Estilos dos Botões do Modal */ + /* ================================================================== */ + + /* Botão Primário com gradiente azul-roxo */ + .form-button-primary { + @apply inline-flex items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-semibold text-white shadow-sm; + background: linear-gradient(135deg, #5bb5f0 0%, #2e9ae5 50%, #a8a3e8 100%); + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + } + + .form-button-primary::before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.3), transparent); + opacity: 0; + transition: opacity 0.2s ease; + } + + .form-button-primary:hover { + box-shadow: 0 4px 12px rgba(91, 181, 240, 0.4); + transform: translateY(-1px); + } + + .form-button-primary:hover::before { + opacity: 1; + } + + .form-button-primary:active { + transform: translateY(0); + box-shadow: 0 2px 8px rgba(91, 181, 240, 0.3); + } + + .form-button-primary:focus-visible { + outline: 2px solid #5bb5f0; + outline-offset: 2px; + } + + .form-button-primary:disabled { + background: linear-gradient(135deg, #bbdefb 0%, #e1bee7 100%); + cursor: not-allowed; + transform: none; + opacity: 0.6; + } + + /* Botão Secundário com borda azul */ + .form-button-secondary { + @apply rounded-md bg-white bg-gray-800 px-4 py-2 text-sm font-semibold text-gray-900 text-gray-100 shadow-sm; + border: 1px solid #bbdefb; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + } + + .form-button-secondary:hover { + background: linear-gradient(135deg, #f8fbff 0%, #f5f8ff 100%); + border-color: #5bb5f0; + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(91, 181, 240, 0.15); + } + + .form-button-secondary:active { + transform: translateY(0); + box-shadow: 0 2px 4px rgba(91, 181, 240, 0.1); + } + + .form-button-secondary:focus-visible { + outline: 2px solid #5bb5f0; + outline-offset: 2px; + } + + @media (prefers-color-scheme: dark) { + .form-button-secondary { + border-color: rgba(91, 181, 240, 0.3); + } + + .form-button-secondary:hover { + background: linear-gradient(135deg, rgba(91, 181, 240, 0.05) 0%, rgba(168, 163, 232, 0.05) 100%); + border-color: #5bb5f0; + } + } + + /* ================================================================== */ + /* Animações */ + /* ================================================================== */ + + @keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } + + @keyframes slideInUp { + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes checkboxPop { + 0% { + transform: scale(1); + } + + 50% { + transform: scale(1.15); + } + + 100% { + transform: scale(1); + } + } + + /* ================================================================== */ + /* Estados de Validação */ + /* ================================================================== */ + + .form-input.is-invalid { + ring-color: #ef5350; + } + + .form-input.is-valid { + ring-color: #66bb6a; + } + + .form-error-message { + @apply mt-2 text-sm; + color: #ef5350; + animation: slideInUp 0.2s ease-out; + } + + .form-helper-text { + @apply mt-2 text-sm text-gray-500 text-gray-400; + } +} \ No newline at end of file diff --git a/resources/css/header.css b/resources/css/header.css new file mode 100644 index 0000000..b7a6f26 --- /dev/null +++ b/resources/css/header.css @@ -0,0 +1,122 @@ +@layer components { + + /* Header - Navbar */ + .nav-bar { + @apply flex flex-nowrap justify-center items-center mx-auto px-4 sm:px-6 lg:px-8 h-10; + + /* REMOVIDO: @apply relative; */ + + /* 1. ADICIONADO: 'top-0 left-0' para "colar" no topo */ + @apply fixed top-0 left-0 z-50 w-full; + @apply bg-transparent text-black; + @apply border-b border-transparent; + @apply transition-colors duration-700 ease-in-out; + } + + .nav-bar::before { + content: ''; + @apply absolute top-0 left-0 w-full h-full; + + /* 2. ESTADO BASE (Topo): Fundo e Sombra */ + @apply bg-white; + @apply shadow-md shadow-blue-400; + /* 4. REMOVIDO: As classes 'border-b border-white' saíram daqui */ + + /* 3. TRANSIÇÃO (Correto) */ + @apply transition-all duration-700 ease-in-out; + + @apply -z-10; + } + + + .navbar-scrolled { + @apply border-white shadow-md; + } + + .navbar-scrolled::before { + @apply opacity-0; + @apply shadow-none; + } + + .nav-bar>.navbar-items>a:hover, + .nav-bar>.navbar-items>a { + @apply transition-all duration-300 transform hover:scale-105; + @apply mr-7; + @apply hover:border hover:shadow-md shadow-blue-400 border-blue-300 rounded-md p-1 transition-all duration-250 transform hover:scale-105; + } + + .nav-bar-logo { + /* Garante que a largura não ultrapasse 100% */ + @apply rounded-md max-w-25 mr-10; + height: auto; + /* Altura proporcional à largura */ + } + + .navbar-items { + @apply flex flex-nowrap justify-center items-center px-4 sm:px-6 lg:px-8 h-10; + } + + .profile-menu { + @apply absolute right-5; + @apply rounded-xl; + @apply max-w-7 max-h-7; + } + + .profile-list-items { + /* 1. Posicionamento (Correto) */ + @apply absolute top-full right-0 ml-4 mt-2; + + @apply block w-full; + /* Adicionei mt-2 para descolar do ícone */ + + /* 2. Largura Fixa (Mais limpo) */ + @apply w-56; + + /* 3. Estilos do Contêiner (Profissional) */ + @apply bg-white p-2 rounded-md shadow-xl; + /* Sombra mais forte */ + @apply border border-gray-200; + + /* 4. Divisórias Sutis entre os
  • */ + @apply divide-y divide-gray-100; + } + + .profile-link { + /* Você pode aplicar isso direto no */ + /* 1. FAZ O LINK PREENCHER O
  • */ + @apply block w-full; + + } + + .profile-items { + /* 1. Limpeza (Classes antigas removidas) */ + + /* 2. Espaçamento Interno (Respiro) */ + @apply px-4 py-2; + + /* 3. Estilos de Texto */ + @apply text-sm text-gray-700; + + /* 4. Interatividade */ + @apply hover:bg-blue-100 cursor-pointer; + + /* 5. Transição Suave */ + @apply transition-colors duration-150 ease-in-out; + + /* 6. Cantos do Hover */ + @apply rounded-md; + } + + .nav-bar-logo img { + @apply w-full; + /* Largura 100% para se ajustar ao container */ + height: auto; + /* Mantém a proporção original da imagem */ + } + + .nav-bar>.navbar-items>form>button { + @apply hover:border hover:shadow-md hover:scale-105 hover:cursor-pointer shadow-blue-400 border-blue-300 rounded-md p-1 transition-all duration-250 transform; + } + + /*End Header - Navbar */ +} \ No newline at end of file diff --git a/resources/css/toast.css b/resources/css/toast.css new file mode 100644 index 0000000..17ec3ef --- /dev/null +++ b/resources/css/toast.css @@ -0,0 +1,149 @@ +@layer components { + /* ================================================================== */ + /* Estilos Aprimorados do Componente TOAST */ + /* resources/css/toast.css */ + /* ================================================================== */ + + .toast-card { + @apply pointer-events-auto flex w-full max-w-sm items-start gap-x-3.5 rounded-xl bg-white shadow-xl ring-1 ring-black/5; + animation: toast-slide-in 0.3s cubic-bezier(0.16, 1, 0.3, 1); + backdrop-filter: blur(8px); + transition: all 0.2s ease; + } + + .toast-card:hover { + @apply shadow-2xl ring-black/10; + transform: translateY(-2px); + } + + @keyframes toast-slide-in { + from { + opacity: 0; + transform: translateX(100%); + } + to { + opacity: 1; + transform: translateX(0); + } + } + + /* Estilo do Toast de Sucesso (Verde) */ + .toast-success { + @apply p-4 relative overflow-hidden; + } + + .toast-success::before { + content: ""; + @apply absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-green-400 to-green-600; + } + + .toast-success .toast-icon-success { + @apply flex-shrink-0 text-green-600 transition-transform duration-200; + filter: drop-shadow(0 2px 4px rgb(34 197 94 / 0.2)); + } + + .toast-success:hover .toast-icon-success { + transform: scale(1.1) rotate(5deg); + } + + .toast-success .toast-message { + @apply w-0 flex-1 text-sm font-semibold text-gray-900 leading-relaxed; + } + + .toast-success .toast-close-button { + @apply inline-flex flex-shrink-0 rounded-lg bg-green-50 text-green-600 hover:bg-green-100 hover:text-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition-all duration-200; + padding: 0.375rem; + } + + .toast-success .toast-close-button:active { + transform: scale(0.95); + } + + /* Estilo do Toast de Erro (Vermelho) */ + .toast-error { + @apply p-4 relative overflow-hidden; + } + + .toast-error::before { + content: ""; + @apply absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-red-400 to-red-600; + } + + .toast-error .toast-icon-error { + @apply flex-shrink-0 text-red-600 transition-transform duration-200; + filter: drop-shadow(0 2px 4px rgb(239 68 68 / 0.2)); + } + + .toast-error:hover .toast-icon-error { + transform: scale(1.1) rotate(-5deg); + } + + .toast-error .toast-message { + @apply w-0 flex-1 text-sm font-semibold text-gray-900 leading-relaxed; + } + + .toast-error .toast-close-button { + @apply inline-flex flex-shrink-0 rounded-lg bg-red-50 text-red-600 hover:bg-red-100 hover:text-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition-all duration-200; + padding: 0.375rem; + } + + .toast-error .toast-close-button:active { + transform: scale(0.95); + } + + /* Animação de saída */ + .toast-exit { + animation: toast-slide-out 0.2s cubic-bezier(0.4, 0, 1, 1) forwards; + } + + @keyframes toast-slide-out { + from { + opacity: 1; + transform: translateX(0); + } + to { + opacity: 0; + transform: translateX(100%); + } + } + + /* Variantes Adicionais (Opcional) */ + .toast-info { + @apply p-4 relative overflow-hidden; + } + + .toast-info::before { + content: ""; + @apply absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-blue-400 to-blue-600; + } + + .toast-warning { + @apply p-4 relative overflow-hidden; + } + + .toast-warning::before { + content: ""; + @apply absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-amber-400 to-amber-600; + } + + /* Dark Mode Support */ + @media (prefers-color-scheme: dark) { + .toast-card { + @apply bg-white ring-black/2; + } + + .toast-success .toast-message, + .toast-error .toast-message { + @apply text-black; + } + + .toast-success .toast-close-button { + @apply bg-blue-900/30 text-blue-400 hover:bg-blue-900/50; + } + + .toast-error .toast-close-button { + @apply bg-red-900/30 text-red-400 hover:bg-red-900/50; + } + } + } + \ No newline at end of file diff --git a/resources/css/user-form-modal.css b/resources/css/user-form-modal.css new file mode 100644 index 0000000..5ebd290 --- /dev/null +++ b/resources/css/user-form-modal.css @@ -0,0 +1,157 @@ +@layer components { + + /* Modal Overlay */ + .modal-overlay { + position: fixed; + inset: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(255, 255, 255, 0.35); + } + + /* Modal Container */ + .modal-container { + background-color: white; + border-radius: 0.5rem; + box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 32rem; + padding: 1.5rem; + } + + /* Titles */ + .modal-title { + font-size: 1.125rem; + font-weight: 500; + color: #111827; + } + + /* Forms */ + .form-wrapper { + margin-top: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; + } + + /* Inputs */ + .form-input { + display: block; + width: 100%; + border: 2px solid #e5e7eb; + border-radius: 0.375rem; + outline: none; + transition: all 0.3s ease-in-out; + } + + .form-input:hover, + .form-input:focus { + border-color: #93c5fd; + } + + /* Labels */ + .form-label { + display: block; + font-size: 0.875rem; + color: #374151; + } + + /* Errors */ + .error-text { + color: #ef4444; + font-size: 0.75rem; + } + + .error-box { + padding: 0.75rem; + background-color: #fee2e2; + color: #b91c1c; + border-radius: 0.375rem; + } + + /* Buttons */ + .btn-cancel { + background-color: #e5e7eb; + padding: 0.5rem 1rem; + border-radius: 0.375rem; + } + + .btn-submit { + background-color: #2563eb; + color: white; + padding: 0.5rem 1rem; + border-radius: 0.375rem; + } + + .loading { + cursor: progress; + } + + /* Select Component */ + .select-wrapper { + position: relative; + margin-top: 0.25rem; + } + + .select-button { + height: 2.5rem; + width: 10rem; + background: white; + border: 1px solid #d1d5db; + border-radius: 0.5rem; + padding: 0.5rem 2.5rem 0.5rem 0.75rem; + text-align: left; + position: relative; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + } + + .select-icon { + position: absolute; + right: 0.5rem; + top: 0; + bottom: 0; + display: flex; + align-items: center; + } + + .select-svg { + width: 1.25rem; + height: 1.25rem; + color: #9ca3af; + } + + .select-options { + position: absolute; + z-index: 10; + margin-top: 0.25rem; + width: 100%; + max-height: 15rem; + overflow-y: auto; + background-color: white; + border-radius: 0.375rem; + box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1); + } + + .select-option { + padding: 0.5rem 2.5rem; + cursor: pointer; + } + + .select-option:hover { + background-color: #dbeafe; + } + + .selected-option { + background-color: #eff6ff; + } + + .form-footer { + display: flex; + justify-content: flex-end; + gap: 0.75rem; + padding-top: 1rem; + } + +} \ No newline at end of file From 875f8566608ead2bddb9dc57eeb5ce799a6c3421 Mon Sep 17 00:00:00 2001 From: lukibeg Date: Sat, 8 Nov 2025 19:52:13 -0300 Subject: [PATCH 03/12] =?UTF-8?q?refactor:=20remo=C3=A7=C3=A3o=20de=20l?= =?UTF-8?q?=C3=B3gica=20de=20neg=C3=B3cio=20duplicada.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Services/ClientService.php | 3 ++- app/Services/UserService.php | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/Services/ClientService.php b/app/Services/ClientService.php index 0b0cbd6..4ed0af6 100644 --- a/app/Services/ClientService.php +++ b/app/Services/ClientService.php @@ -10,7 +10,8 @@ class ClientService { public function __construct(protected Client $client) {} - public function addClient($client) { + public function addClient(array $client) + { return Client::create($client); } } diff --git a/app/Services/UserService.php b/app/Services/UserService.php index bed4b22..d72284f 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -11,10 +11,6 @@ class UserService public function __construct(protected User $user) {} public function createUser(array $user) { - if (User::where('email', '=', $user['email'])) { - throw new \Exception('O e-mail já está cadastrado.'); - } - return User::create($user); } } From b8dadb02925be997b46e53be7b8a374c356e06b2 Mon Sep 17 00:00:00 2001 From: lukibeg Date: Sat, 8 Nov 2025 19:53:27 -0300 Subject: [PATCH 04/12] feat: adiciona novo cliente no banco de dados. --- app/Livewire/Admin/AddClient.php | 65 +++++++++++--------------------- 1 file changed, 23 insertions(+), 42 deletions(-) diff --git a/app/Livewire/Admin/AddClient.php b/app/Livewire/Admin/AddClient.php index ebb3bf5..7b5e395 100644 --- a/app/Livewire/Admin/AddClient.php +++ b/app/Livewire/Admin/AddClient.php @@ -12,58 +12,39 @@ class AddClient extends Component { use WithFileUploads; - // 2. Declara as propriedades principais + // 1. Declara as propriedades principais public ClientForm $form; - public $showModal = false; - - /** - * Ouve o evento para abrir o modal. - * Disparado por: wire:click="$dispatch('openAddClientModal')" - */ - #[On('openAddClientModal')] - public function openModal() - { - $this->form->reset(); // Limpa o formulário de dados antigos - $this->showModal = true; - } - - /** - * Fecha e limpa o modal. - */ - public function closeModal() - { - $this->showModal = false; - $this->form->reset(); - } - /** * Método principal chamado pelo wire:submit="save". */ public function save() { - // 3. Valida os dados usando as 'rules' do ClientForm.php + + + // 2. Valida os dados usando as 'rules' do ClientForm.php $this->form->validate(); - // 4. Pega todos os dados validados - $data = $this->form->all(); + try { + $data = $this->form->all(); + $data['name'] = $data['client_name']; + // 4. Lida com o upload do arquivo de imagem + if ($this->form->profile_image_path) { + $path = $this->form->profile_image_path->store('client_logos', 'public'); + $data['profile_image_path'] = $path; + } - // 5. Lida com o upload do arquivo de imagem - if ($this->form->profile_image_path) { - $path = $this->form->profile_image_path->store('client_logos', 'public'); - $data['profile_image_path'] = $path; + // 5. Cria o cliente no banco de dados + Client::create($data); + // 6. Despacha um evento para atualizar outros componentes (ex: o grid de clientes) + $this->dispatch('client-added'); + // (Opcional) Envia uma notificação de sucesso + $this->dispatch('notify', message: 'Cliente adicionado com sucesso!'); + } catch (\Exception $e) { + dd($e); + $this->dispatch('notify', message: 'Ocorreu um erro inesperado ao salvar.', type: 'error'); } + // 3. Pega todos os dados validados - // 6. Cria o cliente no banco de dados - Client::create($data); - - // 7. Fecha o modal - $this->closeModal(); - - // 8. Despacha um evento para atualizar outros componentes (ex: o grid de clientes) - $this->dispatch('clientAdded'); - - // (Opcional) Envia uma notificação de sucesso - // $this->dispatch('notify', 'Cliente adicionado com sucesso!'); } /** @@ -73,4 +54,4 @@ public function render() { return view('livewire.admin.add-client'); } -} \ No newline at end of file +} From 6d6749964e256e9658610bf2fbf9f3004b4dcd3f Mon Sep 17 00:00:00 2001 From: lukibeg Date: Sat, 8 Nov 2025 19:54:10 -0300 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20valida=20dados=20do=20formul?= =?UTF-8?q?=C3=A1rio=20para=20adi=C3=A7=C3=A3o=20de=20clients.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Livewire/Forms/ClientForm.php | 86 +++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 app/Livewire/Forms/ClientForm.php diff --git a/app/Livewire/Forms/ClientForm.php b/app/Livewire/Forms/ClientForm.php new file mode 100644 index 0000000..2a8c0d0 --- /dev/null +++ b/app/Livewire/Forms/ClientForm.php @@ -0,0 +1,86 @@ +toArray(); + $data['has_call_center'] = (bool) $client->has_call_center; + $data['has_voice_gateway'] = (bool) $client->has_voice_gateway; + $data['has_fop2'] = (bool) $client->has_fop2; + + $this->fill($data); + } + + // 4. ADICIONADO: Método de Regras + /** + * Define as regras de validação para o formulário. + */ + public function rules() + { + return [ + 'client_name' => 'required|string|max:255', + 'legal_name' => 'required|string|max:255', + 'cnpj' => 'required|string|max:20|unique:clients,cnpj', + 'profile_image_path' => 'nullable|file|mimes:jpeg,png,bmp,gif,svg,webp|max:2048', // 2MB Max 'pbx_hosting' => 'nullable|string|max:255', + 'activation_date' => 'nullable|date', + 'carrier' => 'nullable|string|max:255', + 'access_type' => 'nullable|string|max:255', + 'server_ip' => 'nullable|ip', + 'root_password' => 'nullable|string', + 'has_call_center' => 'boolean', + 'has_voice_gateway' => 'boolean', + 'has_fop2' => 'boolean', + 'modules' => 'nullable|json', + 'whatsapp_number' => 'nullable|string|max:20', + 'whatsapp_activation_date' => 'nullable|date', + ]; + } + + // 5. ADICIONADO: Método de Mensagens Customizadas + /** + * Define as mensagens de erro customizadas. + */ + public function messages() + { + return [ + 'client_name.required' => 'O campo Nome Fantasia é obrigatório.', + 'client_name.max' => 'O Nome Fantasia não pode ter mais que 255 caracteres.', + + 'cnpj.unique' => 'Este CNPJ já está cadastrado em outro cliente.', + + 'profile_image_path.image' => 'O arquivo deve ser uma imagem válida (jpg, png, etc.).', + 'profile_image_path.max' => 'A imagem não pode ser maior que 2MB.', + + 'server_ip.ip' => 'Por favor, insira um endereço de IP válido.', + 'modules.json' => 'O campo módulos deve conter um formato JSON válido.', + + '*.date' => 'Por favor, insira uma data válida.', + '*.boolean' => 'Este campo deve ser verdadeiro ou falso.', + ]; + } +} From 42bb548599d60367986587ab4902f2eea0d18b54 Mon Sep 17 00:00:00 2001 From: lukibeg Date: Sat, 8 Nov 2025 19:55:39 -0300 Subject: [PATCH 06/12] =?UTF-8?q?refactor:=20implementa=20novo=20dispatch?= =?UTF-8?q?=20de=20um=20evento=20notify=20para=20exibi=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20toasts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Livewire/Admin/CreateUser.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/Livewire/Admin/CreateUser.php b/app/Livewire/Admin/CreateUser.php index 1418c83..15d39f3 100644 --- a/app/Livewire/Admin/CreateUser.php +++ b/app/Livewire/Admin/CreateUser.php @@ -19,7 +19,7 @@ class CreateUser extends Component // Nota: Adicionei 'same:password' para garantir que as senhas batem. protected $rules = [ 'name' => 'required|string|max:255', - 'email' => 'required|email', + 'email' => 'required|email|unique:users,email', 'password' => 'required|string|min:8', 'password_confirm' => 'required|string|same:password', // <-- Regra importante! 'permission_level' => 'required|boolean' // ou 'required|in:0,1' @@ -28,6 +28,7 @@ class CreateUser extends Component protected $messages = [ 'name' => 'Nome precisa ser informado.', 'email' => 'O email precisa ser informado.', + 'email.unique' => 'O email informado já foi cadastrado anteriormente.', 'password' => 'A senha precisa ter 8 ou mais caracteres.', 'password_confirm' => 'As senhas não coincidem.', 'permission_level' => 'Defina o nível de autorização do usuário.' @@ -59,7 +60,8 @@ public function createUser(UserService $userService) $this->dispatch('user-created'); // Envia a mesma mensagem de sucesso do seu controller - session()->flash('message', 'Usuário cadastrado com sucesso!'); + $this->dispatch('notify', message: 'Usuário cadastrado com sucesso!'); + // (Opcional) Se sua tabela de usuários for outro componente Livewire, // você pode mandar ela atualizar assim: @@ -70,9 +72,6 @@ public function createUser(UserService $userService) // 7. O "Erro" (Tradução do Redirect de Erro) // Em vez de redirecionar, adicionamos o erro ao formulário // para que o usuário veja na tela, sem refresh. - if ($e->getMessage() == 'O e-mail já está cadastrado.') { - $this->addError('email', $e->getMessage()); - } $this->addError('general', $e->getMessage()); } From dde40993fef29e745b0c9cf41e7b7a8e93ef787a Mon Sep 17 00:00:00 2001 From: lukibeg Date: Sat, 8 Nov 2025 19:56:14 -0300 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20Componente=20para=20adi=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20flash=20messages=20ap=C3=B3s=20determinadas=20a?= =?UTF-8?q?=C3=A7=C3=B5es.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/View/Components/FlashMessages.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 app/View/Components/FlashMessages.php diff --git a/app/View/Components/FlashMessages.php b/app/View/Components/FlashMessages.php new file mode 100644 index 0000000..6b5d455 --- /dev/null +++ b/app/View/Components/FlashMessages.php @@ -0,0 +1,26 @@ + Date: Sat, 8 Nov 2025 19:56:58 -0300 Subject: [PATCH 08/12] refactor: refatora e ajusta detalhes do layout. --- resources/views/layouts/app.blade.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 1365cf2..1c08c7d 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -63,6 +63,12 @@
  • +
  • + + Adicionar clientes + +
  • +
    @endauth @@ -73,6 +79,9 @@ + + + @yield('content') @livewireScripts From bb782575fa42a58e75b5fbca903f1756b8151886 Mon Sep 17 00:00:00 2001 From: lukibeg Date: Sat, 8 Nov 2025 19:57:45 -0300 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20Exibe=20flash=20messages=20ap?= =?UTF-8?q?=C3=B3s=20alguma=20a=C3=A7=C3=A3o.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/components/flash-messages.blade.php | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 resources/views/components/flash-messages.blade.php diff --git a/resources/views/components/flash-messages.blade.php b/resources/views/components/flash-messages.blade.php new file mode 100644 index 0000000..d78d96e --- /dev/null +++ b/resources/views/components/flash-messages.blade.php @@ -0,0 +1,46 @@ +
    + +
    \ No newline at end of file From 74ba08dd965d22fc21b3748e02043f285c01b0d4 Mon Sep 17 00:00:00 2001 From: lukibeg Date: Sat, 8 Nov 2025 19:58:25 -0300 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20formul=C3=A1rio=20para=20adi?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20clientes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/livewire/admin/add-client.blade.php | 151 ++++++++++-------- 1 file changed, 87 insertions(+), 64 deletions(-) diff --git a/resources/views/livewire/admin/add-client.blade.php b/resources/views/livewire/admin/add-client.blade.php index 8071661..4b62956 100644 --- a/resources/views/livewire/admin/add-client.blade.php +++ b/resources/views/livewire/admin/add-client.blade.php @@ -1,38 +1,31 @@ -
    +
    -
    +
    + +
    +
    -
    -
    - -
    + +
    -

    +

    Adicionar Novo Cliente

    - @@ -41,37 +34,46 @@ class="rounded-md bg-blue-600 text-blue-200 hover:text-white focus:outline-none
    - +
    - +

    Informações Principais

    - +
    - - - @error('form.name') {{ $message }} @enderror + + + @error('form.client_name') {{ $message }} + @enderror
    - - - @error('form.legal_name') {{ $message }} @enderror + + + @error('form.legal_name') {{ $message }} @enderror
    - + - @error('form.cnpj') {{ $message }} @enderror + @error('form.cnpj') {{ $message }} + @enderror
    - - -
    + + +
    Enviando...
    - @error('form.profile_image_path') {{ $message }} @enderror + @error('form.profile_image_path') {{ $message }} @enderror
    @@ -82,11 +84,13 @@ class="rounded-md bg-blue-600 text-blue-200 hover:text-white focus:outline-none
    - +
    - +
    @@ -94,20 +98,24 @@ class="rounded-md bg-blue-600 text-blue-200 hover:text-white focus:outline-none
    - +
    - - @error('form.server_ip') {{ $message }} @enderror + + @error('form.server_ip') {{ $message }} @enderror
    - +
    - +
    @@ -115,25 +123,31 @@ class="rounded-md bg-blue-600 text-blue-200 hover:text-white focus:outline-none
    - +
    - - + +
    -
    +
    +

    Módulos & Recursos

    - - @error('form.modules') {{ $message }} @enderror + + @error('form.modules') {{ $message }} + @enderror
    -
    +
    +
    +
    +
    - - - +
    @@ -166,4 +187,6 @@ class="rounded-md bg-blue-600 text-blue-200 hover:text-white focus:outline-none
    + +
    \ No newline at end of file From 607d6c3e2536085ee20e1e23c9c13f6b8ba7629a Mon Sep 17 00:00:00 2001 From: lukibeg Date: Sat, 8 Nov 2025 19:59:18 -0300 Subject: [PATCH 11/12] =?UTF-8?q?refactor:=20Separa=C3=A7=C3=A3o=20entre?= =?UTF-8?q?=20estilos=20e=20html,=20al=C3=A9m=20de=20pequenas=20altera?= =?UTF-8?q?=C3=A7=C3=B5es=20visuais.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../livewire/admin/create-user.blade.php | 136 ++++++++---------- 1 file changed, 57 insertions(+), 79 deletions(-) diff --git a/resources/views/livewire/admin/create-user.blade.php b/resources/views/livewire/admin/create-user.blade.php index 6dd13ed..f0db43e 100644 --- a/resources/views/livewire/admin/create-user.blade.php +++ b/resources/views/livewire/admin/create-user.blade.php @@ -1,128 +1,106 @@ -
    + +
    - @if (session('message')) -
    - {{ session('message') }} -
    - @endif + @endsection \ No newline at end of file