feat: Autorização implementada em todos os controladores.

feat: Autorização em todos os controladores.
This commit is contained in:
lukidev 2025-11-27 20:43:47 -03:00 committed by GitHub
commit 0b115a7bdf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 469 additions and 143 deletions

View File

@ -18,9 +18,11 @@ class AddClient extends Component
public function save(ClientService $clientService) public function save(ClientService $clientService)
{ {
$this->form->validate();
try { try {
$this->authorize('addClient', Auth::user());
$this->form->validate();
$data = $this->form->all(); $data = $this->form->all();
$data['name'] = $data['client_name']; $data['name'] = $data['client_name'];
@ -36,7 +38,7 @@ public function save(ClientService $clientService)
$this->dispatch('client-added'); $this->dispatch('client-added');
$this->dispatch('notify', message: $client->name . ' adicionado com sucesso!'); $this->dispatch('notify', message: $client->name . ' adicionado com sucesso!');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->dispatch('notify', message: 'Ocorreu um erro inesperado ao salvar.', type: 'error'); $this->dispatch('notify', message: 'Ocorreu um erro inesperado ao salvar. ' . $e->getMessage(), type: 'error');
} }
} }

View File

@ -5,12 +5,17 @@
use Livewire\Component; use Livewire\Component;
use Livewire\Attributes\On; use Livewire\Attributes\On;
use App\Models\Client; use App\Models\Client;
use Exception;
use Illuminate\Support\Facades\Auth;
class DeleteClient extends Component class DeleteClient extends Component
{ {
#[On('confirm-delete')] #[On('confirm-delete')]
public function deleteClient($payload) public function deleteClient($payload)
{ {
try {
// Sua lógica de autorização e exclusão (Correta)
$this->authorize('deleteClient', Auth::user());
$deletedClient = Client::findOrFail($payload); $deletedClient = Client::findOrFail($payload);
@ -18,9 +23,14 @@ public function deleteClient($payload)
$deletedClient->delete(); $deletedClient->delete();
} }
// Sucesso (Dentro do try, onde deve estar)
$this->dispatch('client-deleted'); $this->dispatch('client-deleted');
// (Opcional) Envia uma notificação de sucesso
$this->dispatch('notify', message: 'Cliente excluído com sucesso!'); $this->dispatch('notify', message: 'Cliente excluído com sucesso!');
} catch (Exception $e) {
// Tratamento de erro
$this->dispatch('notify', message: 'Você não possui permissão para realizar essa ação.', type: 'error');
}
} }
public function render() public function render()

View File

@ -0,0 +1,30 @@
<?php
namespace App\Livewire\Admin\Client;
use App\Livewire\Forms\ClientForm;
use Livewire\Component;
use Livewire\Attributes\On;
use App\Models\Client;
use Illuminate\Support\Facades\Crypt;
class DetailClient extends Component
{
public ClientForm $clientForm;
public ?Client $client = null;
#[On('client-detail')]
public function detailClient($id)
{
$this->client = Client::findOrFail($id);
$decryptedPassword = Crypt::decryptString($this->client->root_password);
$this->client->root_password = $decryptedPassword;
}
public function render()
{
return view('livewire.admin.clients.detail-client');
}
}

View File

@ -5,6 +5,7 @@
use App\Livewire\Forms\ClientForm; use App\Livewire\Forms\ClientForm;
use App\Models\Client; use App\Models\Client;
use App\Services\ClientService; use App\Services\ClientService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Livewire\Attributes\On; use Livewire\Attributes\On;
use Livewire\Component; use Livewire\Component;
@ -18,11 +19,8 @@ class EditClient extends Component
public Client $client; public Client $client;
public ClientForm $clientForm; public ClientForm $clientForm;
// public ClientService $clientService;
#[On('update-client')] #[On('update-client')]
public function loadClient($id) public function loadClient($id)
{ {
try { try {
@ -37,6 +35,10 @@ public function loadClient($id)
} }
public function edit(ClientService $clientService) public function edit(ClientService $clientService)
{ {
try {
$this->authorize('editClient', Auth::user());
$data = $this->clientForm->validate(); $data = $this->clientForm->validate();
if ($this->clientForm->profile_image_path) { if ($this->clientForm->profile_image_path) {
@ -46,8 +48,6 @@ public function edit(ClientService $clientService)
$data['root_password'] = Crypt::encryptString($data['root_password']); $data['root_password'] = Crypt::encryptString($data['root_password']);
try {
if (!$clientService->updateClient($this->client, $data)) { if (!$clientService->updateClient($this->client, $data)) {
throw new Exception('O serviço não confirmou a atualização.'); throw new Exception('O serviço não confirmou a atualização.');
} }

View File

@ -34,11 +34,10 @@ class CreateUser extends Component
public function createUser(UserService $userService) public function createUser(UserService $userService)
{ {
try {
$this->authorize('createUser', Auth::user());
$validated = $this->validate($this->rules, $this->messages); $validated = $this->validate($this->rules, $this->messages);
try {
$this->authorize('createUser', Auth::user());
$user = $userService->createUser($validated); $user = $userService->createUser($validated);
@ -49,6 +48,7 @@ public function createUser(UserService $userService)
$this->dispatch('notify', message: 'Usuário cadastrado com sucesso!'); $this->dispatch('notify', message: 'Usuário cadastrado com sucesso!');
} catch (\Exception $e) { } catch (\Exception $e) {
$this->addError('general', $e->getMessage()); $this->addError('general', $e->getMessage());
$this->dispatch('notify', message: 'Ocorreu um erro ao criar o usuário. ' . $e->getMessage(), type: 'error');
} }
} }

View File

@ -6,6 +6,7 @@
use Livewire\Attributes\On; use Livewire\Attributes\On;
use App\Models\User; use App\Models\User;
use App\Services\UserService; use App\Services\UserService;
use Illuminate\Support\Facades\Auth;
use Exception; use Exception;
class DeleteUser extends Component class DeleteUser extends Component
@ -14,9 +15,11 @@ class DeleteUser extends Component
#[On('confirm-delete-user')] #[On('confirm-delete-user')]
public function deleteUser(UserService $userService, $payload) public function deleteUser(UserService $userService, $payload)
{ {
try {
$this->authorize('deleteUser', Auth::user());
$deletedUser = User::findOrFail($payload); $deletedUser = User::findOrFail($payload);
try {
if ($deletedUser) { if ($deletedUser) {
$deletedUser = $userService->deleteUser($deletedUser); $deletedUser = $userService->deleteUser($deletedUser);
} }
@ -25,7 +28,7 @@ public function deleteUser(UserService $userService, $payload)
$this->dispatch('notify', message: $deletedUser->name . ' Usuário excluído com sucesso!'); $this->dispatch('notify', message: $deletedUser->name . ' Usuário excluído com sucesso!');
} catch (Exception $e) { } catch (Exception $e) {
$this->dispatch('user-delete-error'); $this->dispatch('user-delete-error');
$this->dispatch('notify', message: $e->getMessage()); $this->dispatch('notify', message: $e->getMessage(), type: 'error');
} }
} }

View File

@ -7,6 +7,7 @@
use Livewire\Component; use Livewire\Component;
use Livewire\Attributes\On; use Livewire\Attributes\On;
use App\Services\UserService; use App\Services\UserService;
use Illuminate\Support\Facades\Auth;
class EditUser extends Component class EditUser extends Component
{ {
@ -31,8 +32,11 @@ public function loadUser($id)
} }
public function editUser(UserService $userService) public function editUser(UserService $userService)
{ {
$data = $this->userForm->validate();
try { try {
$this->authorize('editUser', Auth::user());
$data = $this->userForm->validate();
if (!$userService->updateUser($this->user, $data)) { if (!$userService->updateUser($this->user, $data)) {
throw new \Exception('O serviço não confirmou a atualização.'); throw new \Exception('O serviço não confirmou a atualização.');
} }

View File

@ -28,6 +28,9 @@ class Client extends Model
'carrier', 'carrier',
'access_type', 'access_type',
'server_ip', 'server_ip',
'port',
'ddr_key',
'ddr_out',
'root_password', // Lembre-se de criptografar isso! 'root_password', // Lembre-se de criptografar isso!
'has_call_center', 'has_call_center',
'has_voice_gateway', 'has_voice_gateway',
@ -35,8 +38,18 @@ class Client extends Model
'modules', 'modules',
'whatsapp_number', 'whatsapp_number',
'whatsapp_activation_date', 'whatsapp_activation_date',
'observation',
]; ];
/*
- Número chave, número de saída
- Credenciais de acesso VPN
- Anexos
- Porta de acesso
*/
/** /**
* Os atributos que devem ser convertidos para tipos nativos. * Os atributos que devem ser convertidos para tipos nativos.
* *

View File

@ -22,8 +22,21 @@ public function register(): void
*/ */
public function boot(): void public function boot(): void
{ {
Gate::define('createUser', function (User $user) { // Lista de todas as ações que são exclusivas de Admin
return isset($user->permissions) ? in_array('admin', $user->permissions) : false; $adminActions = [
'createUser',
'editUser',
'deleteUser',
'addClient', // Exemplo
'deleteClient', // Exemplo
'editClient',
];
foreach ($adminActions as $action) {
Gate::define($action, function (User $user) {
// A lógica fica centralizada aqui. Se mudar, muda pra todos.
return isset($user->permissions) && in_array('admin', $user->permissions);
}); });
} }
} }
}

View File

@ -25,4 +25,10 @@ public function updateClient(Client $client, $data)
$data['name'] = $data['client_name']; $data['name'] = $data['client_name'];
return $client->update($data); return $client->update($data);
} }
public function detailClient(Client $client)
{
//Detail method
return $client->all();
}
} }

View File

@ -13,6 +13,8 @@ class UserService
public function __construct(protected User $user) {} public function __construct(protected User $user) {}
public function createUser(array $user) public function createUser(array $user)
{ {
$permissions = [$user['permissions']];
$user['permissions'] = $permissions;
return User::create($user); return User::create($user);
} }

View File

@ -12,7 +12,7 @@
public function up(): void public function up(): void
{ {
Schema::create('users', function (Blueprint $table) { Schema::create('users', function (Blueprint $table) {
$table->id(); $table->uuid('id')->primary();
$table->string('name'); $table->string('name');
$table->json('permissions')->nullable(); $table->json('permissions')->nullable();
$table->string('email')->unique(); $table->string('email')->unique();

View File

@ -12,16 +12,13 @@
public function up(): void public function up(): void
{ {
Schema::create('clients', function (Blueprint $table) { Schema::create('clients', function (Blueprint $table) {
// 1. Alterado de $table->id() para $table->uuid()
$table->uuid('id')->primary(); $table->uuid('id')->primary();
// Informações de Cadastro
$table->string('name'); $table->string('name');
$table->string('legal_name'); $table->string('legal_name');
$table->string('cnpj')->unique()->nullable(); $table->string('cnpj')->unique()->nullable();
// 2. Adicionado o path da imagem $table->string('profile_image_path')->nullable();
$table->string('profile_image_path')->nullable(); // "path da imagem do cliente"
// Informações Técnicas // Informações Técnicas
$table->string('pbx_hosting')->nullable(); $table->string('pbx_hosting')->nullable();
@ -30,13 +27,14 @@ public function up(): void
$table->string('access_type')->nullable(); $table->string('access_type')->nullable();
$table->ipAddress('server_ip')->nullable(); $table->ipAddress('server_ip')->nullable();
$table->text('root_password')->nullable(); $table->text('root_password')->nullable();
$table->text('ddr_out')->nullable();
$table->text('ddr_key')->nullable();
$table->longText('observation')->nullable();
// Módulos e Features
$table->boolean('has_call_center')->default(false); $table->boolean('has_call_center')->default(false);
$table->boolean('has_voice_gateway')->default(false); $table->boolean('has_voice_gateway')->default(false);
$table->boolean('has_fop2')->default(false); $table->boolean('has_fop2')->default(false);
// Módulos
$table->json('modules')->nullable(); $table->json('modules')->nullable();
$table->string('whatsapp_number')->nullable(); $table->string('whatsapp_number')->nullable();
$table->date('whatsapp_activation_date')->nullable(); $table->date('whatsapp_activation_date')->nullable();
@ -46,9 +44,6 @@ public function up(): void
}); });
} }
/**
* Reverse the migrations.
*/
public function down(): void public function down(): void
{ {
Schema::dropIfExists('clients'); Schema::dropIfExists('clients');

View File

@ -12,7 +12,7 @@
} }
}" @notify.window="addToast($event.detail.message, $event.detail.type || 'success')" }" @notify.window="addToast($event.detail.message, $event.detail.type || 'success')"
@notifyError.window="addToast($event.detail.message, $event.detail.type || 'error')" @notifyError.window="addToast($event.detail.message, $event.detail.type || 'error')"
class="fixed top-5 right-5 z-50 flex w-full max-w-xs flex-col space-y-3"> class="fixed top-5 right-5 z-50000 flex w-full max-w-xs flex-col space-y-3">
<template x-for="toast in toasts" :key="toast.id"> <template x-for="toast in toasts" :key="toast.id">
<div x-show="true" x-transition:enter="transform ease-out duration-300 transition" <div x-show="true" x-transition:enter="transform ease-out duration-300 transition"
x-transition:enter-start="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" x-transition:enter-start="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
@ -35,7 +35,7 @@ class="fixed top-5 right-5 z-50 flex w-full max-w-xs flex-col space-y-3">
<p class="toast-message" x-text="toast.message"></p> <p class="toast-message" x-text="toast.message"></p>
<button @click="removeToast(toast.id)" class="toast-close-button"> <button @click="removeToast(toast.id)" class="toast-close-button cursor-pointer">
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20"> <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
<path <path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" /> d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />

View File

@ -8,6 +8,7 @@
<livewire:admin.client.add-client /> <livewire:admin.client.add-client />
<livewire:admin.user.delete-user> <livewire:admin.user.delete-user>
<livewire:admin.show-users /> <livewire:admin.show-users />
<livewire:admin.client.detail-client />
<livewire:admin.client.delete-client /> <livewire:admin.client.delete-client />
<x-are-you-sure /> <x-are-you-sure />
<livewire:show-client /> <livewire:show-client />

View File

@ -76,6 +76,12 @@
</a> </a>
</li> </li>
<li class="profile-items">
<a @click="$dispatch('vpn-show-links')" class="profile-link">
VPN's
</a>
</li>
</ul> </ul>
</div> </div>
@endauth @endauth

View File

@ -22,7 +22,7 @@ class="fixed inset-y-0 right-0 flex max-w-full pl-10" @click.away="showClientMod
Adicionar Novo Cliente Adicionar Novo Cliente
</h2> </h2>
<button type="button" @click="showClientModal = false" <button type="button" @click="showClientModal = false"
class="rounded-md text-blue-200 hover:text-white cursor-pointer focus:outline-none focus:ring-2 focus:ring-white"> class="rounded-md text-blue-400 hover:text-white cursor-pointer focus:outline-none focus:ring-2 focus:ring-white">
<span class="sr-only">Fechar painel</span> <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" <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"> stroke-width="1.5" stroke="currentColor">
@ -44,7 +44,7 @@ class="rounded-md text-blue-200 hover:text-white cursor-pointer focus:outline-no
<div> <div>
<label for="client_name" class="form-label">Nome Fantasia *</label> <label for="client_name" class="form-label">Nome Fantasia *</label>
<input type="text" wire:model="form.client_name" id="client_name" <input type="text" wire:model="form.client_name" id="client_name"
class="form-input"> class="form-input" placeholder="Eletrônicos Mota">
@error('form.client_name') <span @error('form.client_name') <span
class="text-red-500 text-sm">{{ $message }}</span> class="text-red-500 text-sm">{{ $message }}</span>
@enderror @enderror
@ -52,14 +52,14 @@ class="text-red-500 text-sm">{{ $message }}</span>
<div> <div>
<label for="legal_name" class="form-label">Razão Social *</label> <label for="legal_name" class="form-label">Razão Social *</label>
<input type="text" wire:model="form.legal_name" id="legal_name" <input type="text" wire:model="form.legal_name" id="legal_name"
class="form-input"> class="form-input" placeholder="Mota Germano Eletrônicos LTDA">
@error('form.legal_name') <span @error('form.legal_name') <span
class="text-red-500 text-sm">{{ $message }}</span> @enderror class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div> </div>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<label for="cnpj" class="form-label">CNPJ *</label> <label for="cnpj" class="form-label">CNPJ *</label>
<input type="text" wire:model="form.cnpj" id="cnpj" class="form-input"> <input type="text" wire:model="form.cnpj" id="cnpj" class="form-input" placeholder="54.505.316/0001-64">
@error('form.cnpj') <span class="text-red-500 text-sm">{{ $message }}</span> @error('form.cnpj') <span class="text-red-500 text-sm">{{ $message }}</span>
@enderror @enderror
</div> </div>
@ -94,37 +94,61 @@ class="form-input">
</div> </div>
<div> <div>
<label for="carrier" class="form-label">Operadora</label> <label for="carrier" class="form-label">Operadora</label>
<input type="text" wire:model="form.carrier" id="carrier" class="form-input"> <input type="text" wire:model="form.carrier" id="carrier" class="form-input" placeholder="ALGAR">
</div> </div>
<div> <div>
<label for="access_type" class="form-label">Tipo de Acesso</label> <label for="access_type" class="form-label">Tipo de Acesso</label>
<input type="text" wire:model="form.access_type" id="access_type" <input type="text" wire:model="form.access_type" id="access_type"
class.="form-input" placeholder="Ex: SSH, AnyDesk, VPN"> class.="form-input" placeholder="Ex: SSH, AnyDesk, VPN">
</div> </div>
<div>
<label for="ddr_out" class="form-label">Número de saída chave</label>
<input type="text" wire:model="form.ddr_out" id="ddr_out"
class="form-input" placeholder="(71) 3034-8350">
</div>
<div>
<label for="ddr_key" class="form-label">Número de entrada chave</label>
<input type="text" wire:model="form.ddr_key" id="ddr_key"
class="form-input" placeholder="(71) 3034-8350">
</div>
<div> <div>
<label for="server_ip" class="form-label">IP do Servidor</label> <label for="server_ip" class="form-label">IP do Servidor</label>
<input type="text" wire:model="form.server_ip" id="server_ip" <input type="text" wire:model="form.server_ip" id="server_ip"
class="form-input"> class="form-input" placeholder="10.0.0.1">
@error('form.server_ip') <span @error('form.server_ip') <span
class="text-red-500 text-sm">{{ $message }}</span> @enderror class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div> </div>
<div> <div>
<label for="root_password" class="form-label">Senha (root)</label> <label for="root_password" class="form-label">Senha (root)</label>
<input type="password" wire:model="form.root_password" id="root_password" <input type="password" wire:model="form.root_password" id="root_password"
class="form-input"> class="form-input" placeholder="************">
</div> </div>
</div> </div>
</div> </div>
<hr class="form-divider"> <hr class="form-divider">
<div class="form-group">
<h2 class="form-section-title">Observações e Credenciais Internas</h2>
<div>
<label for="observation" class="form-label">Detalhes da Configuração, VPN, ou Notas</label>
<textarea wire:model="form.observation" id="observation" rows="5" class="form-input" placeholder="VPN: Fortclient, SENHA: *******"></textarea>
@error('form.observation') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
</div>
<hr class="form-divider">
<div class="form-group"> <div class="form-group">
<h2 class="form-section-title">WhatsApp</h2> <h2 class="form-section-title">WhatsApp</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<label for="whatsapp_number" class="form-label">Número do WhatsApp</label> <label for="whatsapp_number" class="form-label">Número do WhatsApp</label>
<input type="text" wire:model="form.whatsapp_number" id="whatsapp_number" <input type="text" wire:model="form.whatsapp_number" id="whatsapp_number"
class="form-input"> class="form-input" placeholder="(71) 3034-8350">
</div> </div>
<div> <div>
<label for="whatsapp_activation_date" class="form-label">Data Ativação <label for="whatsapp_activation_date" class="form-label">Data Ativação

View File

@ -0,0 +1,197 @@
<div x-data="{ showClientDetails: false }" x-cloak
@keydown.escape.window="showClientDetails = false"
x-on:client-detail.window="showClientDetails = true"
class="relative z-50"
>
<div x-show="showClientDetails"
x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-25"
x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-0" x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-white/50 transition-opacity"
></div>
<div x-show="showClientDetails"
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="showClientDetails = false"
>
<div class="w-screen max-w-4xl">
<div class="flex h-full flex-col overflow-y-scroll bg-white shadow-xl">
<div class="bg-gray px-4 py-6 sm:px-6">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold leading-6 text-black" id="slide-over-title">
Detalhes do cliente
</h2>
<button type="button" @click="showClientDetails = false"
class="rounded-md text-blue-400 hover:text-white cursor-pointer 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>
@if($client)
<div class="form-wrapper-modal">
<div class="form-grid-container">
<div class="form-main-panel-modal space-y-8"> <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 mt-4">
<div>
<label class="form-label">Nome Fantasia</label>
<input type="text" value="{{ $client->name }}" class="form-input bg-gray-50" placeholder="Eletrônicos Mota" readonly>
</div>
<div>
<label class="form-label">Razão Social</label>
<input type="text" value="{{ $client->legal_name }}" class="form-input bg-gray-50" placeholder="Mota Germano Eletrônicos LTDA" readonly>
</div>
<div>
<label class="form-label">CNPJ</label>
<input type="text" value="{{ $client->cnpj }}" class="form-input bg-gray-50" placeholder="54.505.316/0001-64" readonly>
</div>
<div>
<label class="form-label">Logo</label>
<div class="mt-1 flex items-center">
@if($client->profile_image_path)
<div class="h-10 w-10 rounded overflow-hidden border border-gray-200">
<img src="{{ asset('storage/' . $client->profile_image_path) }}" alt="Logo" class="h-full w-full object-cover">
</div>
<a href="{{ asset('storage/' . $client->profile_image_path) }}" target="_blank" class="ml-3 text-sm text-blue-600 hover:underline">Abrir imagem</a>
@else
<span class="text-sm text-gray-400 italic p-2 border border-dashed border-gray-300 rounded">Sem logo</span>
@endif
</div>
</div>
</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 mt-4">
<div>
<label class="form-label">Hospedagem PBX</label>
<input type="text" value="{{ $client->pbx_hosting }}" placeholder="Ex: Vultr, Local, AWS" class="form-input bg-gray-50" readonly>
</div>
<div>
<label class="form-label">Data de Ativação</label>
<input type="text" value="{{ $client->activation_date ? $client->activation_date->format('d/m/Y') : '-' }}" class="form-input bg-gray-50" readonly>
</div>
<div>
<label class="form-label">Operadora</label>
<input type="text" value="{{ $client->carrier }}" placeholder="ALGAR" class="form-input bg-gray-50" readonly>
</div>
<div>
<label class="form-label">Tipo de Acesso</label>
<input type="text" value="{{ $client->access_type }}" placeholder="Ex: SSH, AnyDesk, VPN" class="form-input bg-gray-50" readonly>
</div>
<div>
<label class="form-label">Número de saída chave</label>
<input type="text" value="{{ $client->ddr_out }}" placeholder="(71) 3034-8350" class="form-input bg-gray-50 font-mono text-sm" readonly>
</div>
<div>
<label class="form-label">Número de entrada chave</label>
<input type="text" value="{{ $client->ddr_key }}" placeholder="(71) 3034-8350" class="form-input bg-gray-50 font-mono text-sm" readonly>
</div>
<div>
<label class="form-label">IP do Servidor</label>
<input type="text" value="{{ $client->server_ip }}" placeholder="10.0.0.1" class="form-input bg-gray-50 font-mono cursor-text select-all" readonly>
</div>
<div x-data="{ show: false }">
<label class="form-label">Senha (root)</label>
<div class="relative">
<input placeholder="************" :type="show ? 'text' : 'password'" value="{{ $client->root_password }}" class="form-input bg-gray-50 font-mono pr-20" readonly>
<button type="button" @click="show = !show" class="absolute inset-y-0 right-0 px-3 flex items-center text-xs font-bold uppercase text-blue-600 hover:text-blue-800 cursor-pointer">
<span x-text="show ? 'Ocultar' : 'Mostrar'"></span>
</button>
</div>
</div>
</div>
</div>
<hr class="form-divider">
<div class="form-group">
<h2 class="form-section-title">Observações e Credenciais Internas</h2>
<div class="mt-4">
<textarea rows="5" class="form-input bg-gray-50 resize-none" placeholder="VPN: Fortclient, SENHA: *******" readonly>{{ $client->observation }}</textarea>
</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 mt-4">
<div>
<label class="form-label">Número do WhatsApp</label>
<input type="text" placeholder="(71) 3034-8350" value="{{ $client->whatsapp_number }}" class="form-input bg-gray-50 font-mono" readonly>
</div>
<div>
<label class="form-label">Data Ativação WhatsApp</label>
<input type="text" value="{{ $client->whatsapp_activation_date ? $client->whatsapp_activation_date->format('d/m/Y') : '-' }}" class="form-input bg-gray-50" readonly>
</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 mb-4">Módulos & Recursos</h2>
<div class="form-checkbox-group">
<label class="form-checkbox-item opacity-80">
<input type="checkbox" disabled {{ $client->has_call_center ? 'checked' : '' }} class="form-checkbox">
<span>Possui Call Center?</span>
</label>
<label class="form-checkbox-item opacity-80">
<input type="checkbox" disabled {{ $client->has_voice_gateway ? 'checked' : '' }} class="form-checkbox">
<span>Possui Gateway de Voz?</span>
</label>
<label class="form-checkbox-item opacity-80">
<input type="checkbox" disabled {{ $client->has_fop2 ? 'checked' : '' }} class="form-checkbox">
<span>Possui FOP2?</span>
</label>
</div>
<div class="mt-4">
<label class="form-label">Módulos Adicionais (JSON)</label>
<textarea rows="4" placeholder="Ex: {&quot;bi&quot;: true}" class="form-input bg-gray-50 text-xs font-mono" readonly>{{ json_encode($client->modules, JSON_PRETTY_PRINT) }}</textarea>
</div>
</div>
</div>
</div>
</div>
</div>
@else
<div class="flex-1 flex items-center justify-center h-full">
<div class="text-gray-500 flex flex-col items-center">
<svg class="animate-spin h-8 w-8 text-blue-600 mb-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
<span>Carregando...</span>
</div>
</div>
@endif
<div class="flex-shrink-0 border-t border-gray-200 px-4 py-4 sm:px-6">
<div class="flex justify-end">
<button type="button" @click="showClientDetails = false" class="form-button-secondary">
Fechar
</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -22,7 +22,7 @@ class="fixed inset-y-0 right-0 flex max-w-full pl-10" @click.away="showEditClien
Edição de cliente Edição de cliente
</h2> </h2>
<button type="button" @click="showEditClientModal = false" <button type="button" @click="showEditClientModal = false"
class="rounded-md text-blue-200 hover:text-white cursor-pointer focus:outline-none focus:ring-2 focus:ring-white"> class="rounded-md text-blue-400 hover:text-white cursor-pointer focus:outline-none focus:ring-2 focus:ring-white">
<span class="sr-only">Fechar painel</span> <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" <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"> stroke-width="1.5" stroke="currentColor">
@ -44,14 +44,14 @@ class="rounded-md text-blue-200 hover:text-white cursor-pointer focus:outline-no
<div> <div>
<label for="client_name" class="form-label">Nome Fantasia *</label> <label for="client_name" class="form-label">Nome Fantasia *</label>
<input type="text" wire:model="clientForm.client_name" id="client_name" <input type="text" wire:model="clientForm.client_name" id="client_name"
class="form-input"> class="form-input" placeholder="Eletrônicos Mota">
@error('clientForm.client_name') <span @error('clientForm.client_name') <span
class="text-red-500 text-sm">{{ $message }}</span> class="text-red-500 text-sm">{{ $message }}</span>
@enderror @enderror
</div> </div>
<div> <div>
<label for="legal_name" class="form-label">Razão Social *</label> <label for="legal_name" class="form-label">Razão Social *</label>
<input type="text" wire:model="clientForm.legal_name" id="legal_name" <input type="text" wire:model="clientForm.legal_name" placeholder="Mota Germano Eletrônicos LTDA" id="legal_name"
class="form-input"> class="form-input">
@error('clientForm.legal_name') <span @error('clientForm.legal_name') <span
class="text-red-500 text-sm">{{ $message }}</span> @enderror class="text-red-500 text-sm">{{ $message }}</span> @enderror
@ -59,7 +59,7 @@ class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div> </div>
<div class="mt-4"> <div class="mt-4">
<label for="cnpj" class="form-label">CNPJ *</label> <label for="cnpj" class="form-label">CNPJ *</label>
<input type="text" wire:model="clientForm.cnpj" id="cnpj" class="form-input"> <input placeholder="54.505.316/0001-64" type="text" wire:model="clientForm.cnpj" id="cnpj" class="form-input">
@error('clientForm.cnpj') <span class="text-red-500 text-sm">{{ $message }}</span> @error('clientForm.cnpj') <span class="text-red-500 text-sm">{{ $message }}</span>
@enderror @enderror
</div> </div>
@ -94,37 +94,57 @@ class="form-input">
</div> </div>
<div> <div>
<label for="carrier" class="form-label">Operadora</label> <label for="carrier" class="form-label">Operadora</label>
<input type="text" wire:model="clientForm.carrier" id="carrier" class="form-input"> <input type="text" wire:model="clientForm.carrier" id="carrier" placeholder="ALGAR" class="form-input">
</div> </div>
<div> <div>
<label for="access_type" class="form-label">Tipo de Acesso</label> <label for="access_type" class="form-label">Tipo de Acesso</label>
<input type="text" wire:model="clientForm.access_type" id="access_type" <input type="text" wire:model="clientForm.access_type" id="access_type"
class.="form-input" placeholder="Ex: SSH, AnyDesk, VPN"> class.="form-input" placeholder="Ex: SSH, AnyDesk, VPN">
</div> </div>
<div>
<label for="ddr_out" class="form-label">Número de saída chave</label>
<input type="text" wire:model="clientForm.ddr_out" id="ddr_out"
class="form-input" placeholder="(71) 3034-8350">
</div>
<div>
<label for="ddr_key" class="form-label">Número de entrada chave</label>
<input type="text" wire:model="clientForm.ddr_key" id="ddr_key"
class="form-input" placeholder="(71) 3034-8350">
</div>
<div> <div>
<label for="server_ip" class="form-label">IP do Servidor</label> <label for="server_ip" class="form-label">IP do Servidor</label>
<input type="text" wire:model="clientForm.server_ip" id="server_ip" <input type="text" wire:model="clientForm.server_ip" id="server_ip"
class="form-input"> class="form-input" placeholder="10.0.0.1">
@error('clientForm.server_ip') <span @error('form.server_ip') <span
class="text-red-500 text-sm">{{ $message }}</span> @enderror class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div> </div>
<div> <div>
<label for="root_password" class="form-label">Senha (root)</label> <label for="root_password" class="form-label">Senha (root)</label>
<input type="password" wire:model="clientForm.root_password" id="root_password" <input type="password" wire:model="clientForm.root_password" id="root_password"
class="form-input"> class="form-input" placeholder="************">
</div> </div>
</div> </div>
</div> </div>
<hr class="form-divider"> <hr class="form-divider">
<div class="form-group">
<h2 class="form-section-title">Observações e Credenciais Internas</h2>
<div>
<label for="observation" class="form-label">Detalhes da Configuração, VPN, ou Notas</label>
<textarea wire:model="clientForm.observation" id="observation" rows="5" class="form-input" placeholder="VPN: Fortclient, SENHA: *******"></textarea>
@error('clientForm.observation') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
</div>
</div>
<div class="form-group"> <div class="form-group">
<h2 class="form-section-title">WhatsApp</h2> <h2 class="form-section-title">WhatsApp</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<label for="whatsapp_number" class="form-label">Número do WhatsApp</label> <label for="whatsapp_number" class="form-label">Número do WhatsApp</label>
<input type="text" wire:model="clientForm.whatsapp_number" id="whatsapp_number" <input type="text" wire:model="clientForm.whatsapp_number" id="whatsapp_number"
class="form-input"> class="form-input" placeholder="(71) 3034-8350">
</div> </div>
<div> <div>
<label for="whatsapp_activation_date" class="form-label">Data Ativação <label for="whatsapp_activation_date" class="form-label">Data Ativação

View File

@ -17,7 +17,7 @@ class="w-32 h-32 rounded-full object-cover">
<ul x-show="open" class="client-options-list" x-transition> <ul x-show="open" class="client-options-list" x-transition>
<li><a href="#" class="client-option-item" <li><a href="#" class="client-option-item"
x-on:click.prevent="$dispatch('view-client', { id: '{{ $client->id }}' })">Ver Detalhes</a> x-on:click.prevent="$dispatch('client-detail', { id: '{{ $client->id }}' })">Ver Detalhes</a>
</li> </li>
<li><a href="#" class="client-option-item" <li><a href="#" class="client-option-item"
x-on:click.prevent="$dispatch('update-client', { id: '{{ $client->id }}' })">Editar x-on:click.prevent="$dispatch('update-client', { id: '{{ $client->id }}' })">Editar

View File

@ -16,7 +16,7 @@
<span class="user-email">{{ $user->email }}</span> <span class="user-email">{{ $user->email }}</span>
</div> </div>
<div class="user-details"> <div class="user-details">
{{-- <span class="user-permission">{{ ucfirst($user->permissions) }}</span> --}} {{-- <span class="user-permission">{{ $user->permissions }}</span> --}}
<span class="user-status">Ativo</span> <span class="user-status">Ativo</span>
<div class="user-actions-group"> <div class="user-actions-group">