feat: Cria modal-formulário para adição de clientes.

This commit is contained in:
LukiBeg 2025-11-07 17:07:56 -03:00
parent 8cd8cbe283
commit aecaef1509
11 changed files with 538 additions and 231 deletions

View File

@ -1,23 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Services\ClientService;
use Illuminate\Http\Request;
class AddClientController extends Controller
{
private ClientService $clientService;
public function __construct(ClientService $clientService)
{
$this->clientService = $clientService;
}
public function addClient(Request $request)
{
dd($this->clientService);
}
}

View File

@ -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!');
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\Livewire\Admin;
use App\Models\Client;
use App\Livewire\Forms\ClientForm; // 1. Importa seu Form Object
use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\On;
class AddClient extends Component
{
use WithFileUploads;
// 2. 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
$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');
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\Livewire\Forms;
use Livewire\Form;
use App\Models\Client;
class ClientForm extends Form
{
// 2. ATRIBUTOS REMOVIDOS: Os #[Rule(...)] foram removidos daqui
public $name = '';
public $legal_name = '';
public $cnpj = '';
public $profile_image_path;
public $pbx_hosting = '';
public $activation_date;
public $carrier = '';
public $access_type = '';
public $server_ip = '';
public $root_password = '';
public $has_call_center = false;
public $has_voice_gateway = false;
public $has_fop2 = false;
public $modules = '';
public $whatsapp_number = '';
public $whatsapp_activation_date;
// Método para preencher o formulário (para edição futura)
public function addClient(Client $client)
{
$data = $client->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.',
];
}
}

View File

@ -10,5 +10,7 @@ class ClientService
{
public function __construct(protected Client $client) {}
public function addClient() {}
public function addClient($client) {
return Client::create($client);
}
}

290
composer.lock generated
View File

@ -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"
}

View File

@ -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;

View File

@ -1,11 +1,9 @@
@extends('layouts.app')
@section('content')
<h1>Dashboard</h1>
<livewire:admin.create-user />
<div class="container grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="client-card">
@ -386,6 +384,10 @@ class="w-32 h-32 rounded-full object-cover">
</div>
</div>
<livewire:admin.add-client />
</div>
@endsection

View File

@ -0,0 +1,169 @@
<div x-data="{ showModal: @entangle('showModal') }"
x-cloak
@keydown.escape.window="showModal = false"
class="relative z-50">
<div x-show="showModal"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
<div x-show="showModal"
x-transition:enter="transform transition ease-in-out duration-300 sm:duration-500"
x-transition:enter-start="translate-x-full"
x-transition:enter-end="translate-x-0"
x-transition:leave="transform transition ease-in-out duration-300 sm:duration-500"
x-transition:leave-start="translate-x-0"
x-transition:leave-end="translate-x-full"
class="fixed inset-y-0 right-0 flex max-w-full pl-10"
@click.away="showModal = false"> <div class="w-screen max-w-3xl">
<form wire:submit="save" class="flex h-full flex-col overflow-y-scroll bg-white shadow-xl">
<div class="bg-blue-600 px-4 py-6 sm:px-6">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold leading-6 text-white" id="slide-over-title">
Adicionar Novo Cliente
</h2>
<button type="button" @click="showModal = false"
class="rounded-md bg-blue-600 text-blue-200 hover:text-white focus:outline-none focus:ring-2 focus:ring-white">
<span class="sr-only">Fechar painel</span>
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<div class="form-wrapper-modal">
<div class="form-grid-container">
<div class="form-main-panel-modal">
<div class="form-group">
<h2 class="form-section-title">Informações Principais</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="name" class="form-label">Nome Fantasia</label>
<input type="text" wire:model="form.name" id="name" class="form-input">
@error('form.name') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<div>
<label for="legal_name" class="form-label">Razão Social</label>
<input type="text" wire:model="form.legal_name" id="legal_name" class="form-input">
@error('form.legal_name') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
</div>
<div class="mt-4">
<label for="cnpj" class="form-label">CNPJ</label>
<input type="text" wire:model="form.cnpj" id="cnpj" class="form-input">
@error('form.cnpj') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<div class="mt-4">
<label for="profile_image_path" class="form-label">Logo do Cliente</label>
<input type="file" wire:model="form.profile_image_path" id="profile_image_path" class="form-file-input">
<div wire:loading wire:target="form.profile_image_path" class="text-sm text-blue-600 mt-1">
Enviando...
</div>
@error('form.profile_image_path') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
</div>
<hr class="form-divider">
<div class="form-group">
<h2 class="form-section-title">Detalhes do Servidor PBX</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="pbx_hosting" class="form-label">Hospedagem PBX</label>
<input type="text" wire:model="form.pbx_hosting" id="pbx_hosting" class="form-input" placeholder="Ex: Vultr, Local, AWS">
</div>
<div>
<label for="activation_date" class="form-label">Data de Ativação</label>
<input type="date" wire:model="form.activation_date" id="activation_date" class="form-input">
</div>
<div>
<label for="carrier" class="form-label">Operadora</label>
<input type="text" wire:model="form.carrier" id="carrier" class="form-input">
</div>
<div>
<label for="access_type" class="form-label">Tipo de Acesso</label>
<input type="text" wire:model="form.access_type" id="access_type" class.="form-input" placeholder="Ex: SSH, AnyDesk, VPN">
</div>
<div>
<label for="server_ip" class="form-label">IP do Servidor</label>
<input type="text" wire:model="form.server_ip" id="server_ip" class="form-input">
@error('form.server_ip') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
<div>
<label for="root_password" class="form-label">Senha (root)</label>
<input type="password" wire:model="form.root_password" id="root_password" class="form-input">
</div>
</div>
</div>
<hr class="form-divider">
<div class="form-group">
<h2 class="form-section-title">WhatsApp</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="whatsapp_number" class="form-label">Número do WhatsApp</label>
<input type="text" wire:model="form.whatsapp_number" id="whatsapp_number" class="form-input">
</div>
<div>
<label for="whatsapp_activation_date" class="form-label">Data Ativação WhatsApp</label>
<input type="date" wire:model="form.whatsapp_activation_date" id="whatsapp_activation_date" class="form-input">
</div>
</div>
</div>
</div> <div class="form-sidebar-column">
<div class="form-sticky-sidebar-modal">
<div class="form-section-card">
<h2 class="form-section-title">Módulos & Recursos</h2>
<div class="form-checkbox-group">
<label class="form-checkbox-item">
<input type="checkbox" wire:model="form.has_call_center" class="form-checkbox">
<span>Possui Call Center?</span>
</label>
<label class="form-checkbox-item">
<input type="checkbox" wire:model="form.has_voice_gateway" class="form-checkbox">
<span>Possui Gateway de Voz?</span>
</label>
<label class="form-checkbox-item">
<input type="checkbox" wire:model="form.has_fop2" class="form-checkbox">
<span>Possui FOP2?</span>
</label>
</div>
<div class="mt-4">
<label for="modules" class="form-label">Módulos Adicionais (JSON)</label>
<textarea wire:model="form.modules" id="modules" rows="3" class="form-input" placeholder="Ex: {&quot;bi&quot;: true}"></textarea>
@error('form.modules') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
</div>
</div>
</div> </div> </div> <div class="flex-shrink-0 border-t border-gray-200 px-4 py-4 sm:px-6">
<div class="flex justify-end space-x-3">
<button type="button" @click="showModal = false" class="form-button-secondary">
Cancelar
</button>
<button type="submit" class="form-button-primary">
Salvar Cliente
<div wire:loading wire:target="save" class="ml-2 h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent"></div>
</button>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -2,36 +2,47 @@
@section('title', 'Login')
@section('content')
<div class="container">
<div class="container-title">
<div class="container-form">
<div class="container-title">
</div>
<form action="{{ route('login-post') }}" class="form-class" method="POST">
<div class="flex justify-center">
<h1>Efetue seu login</h1>
</div>
<h1>Efetue seu login</h1>
@if ($errors->any())
<div class="messages">
@if ($errors->any())
<div class="alert alert-danger">
{{ $errors->first() }}
</div>
@endif
@endif
@if (session('error'))
@if (session('error'))
<div class="alert alert-danger">
{{ session('error') }}
</div>
@endif
<form action="{{ route('login-post') }}" class="form-class" method="POST">
@csrf
<input class="form-input-class" type="email" name="email" placeholder="E-mail">
<input class="form-input-class" type="password" name="password" placeholder="Senha">
<div class="flex justify-center">
<button class="form-button-class" type="submit">Login</button>
</div>
</form>
<div class="forget-password">
<a href="#">Esqueceu sua senha?</a>
@endif
</div>
</div>
@csrf
<input class="form-input-class" type="email" name="email" placeholder="E-mail">
<input class="form-input-class" type="password" name="password" placeholder="Senha">
<div class="flex justify-center">
<button class="form-button-class" type="submit">Login</button>
</div>
<div class="flex justify-center">
<div class="forget-password">
<a href="#">Esqueceu sua senha?</a>
</div>
</div>
</form>
</div>
@endsection

View File

@ -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 () {