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..7b5e395 --- /dev/null +++ b/app/Livewire/Admin/AddClient.php @@ -0,0 +1,57 @@ +form->validate(); + + 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. 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 + + } + + /** + * Renderiza a view do modal. + */ + public function render() + { + return view('livewire.admin.add-client'); + } +} 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()); } 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.', + ]; + } +} diff --git a/app/Services/ClientService.php b/app/Services/ClientService.php index 0437a5e..4ed0af6 100644 --- a/app/Services/ClientService.php +++ b/app/Services/ClientService.php @@ -10,5 +10,8 @@ class ClientService { public function __construct(protected Client $client) {} - public function addClient() {} + 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); } } 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 @@ +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; @@ -23,129 +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; - @apply relative; - @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 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 { @@ -157,7 +60,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,73 +85,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; - } - - /* 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-120; - - /* 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 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 diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 3ceabc0..ac2efe6 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -1,10 +1,8 @@ @extends('layouts.app') @section('content') - -

    Dashboard

    - +
    @@ -116,276 +114,6 @@ class="w-32 h-32 rounded-full object-cover">
    -
    - -
    - Cliente 4 -
    -
    - -
    -
    -
    - Avatar do Cliente -
    -
    - - - -
    -
    -
    - Cliente 4 -
    -
    - -
    -
    -
    - Avatar do Cliente -
    -
    - - - -
    -
    -
    - Cliente 4 -
    -
    - -
    -
    -
    - Avatar do Cliente -
    -
    - - - -
    -
    -
    - Cliente 4 -
    -
    - -
    -
    -
    - Avatar do Cliente -
    -
    - - - -
    -
    -
    - Cliente 4 -
    -
    - -
    -
    -
    - Avatar do Cliente -
    -
    - - - -
    -
    -
    - Cliente 4 -
    -
    - -
    -
    -
    - Avatar do Cliente -
    -
    - - - -
    -
    -
    - Cliente 4 -
    -
    - -
    -
    -
    - Avatar do Cliente -
    -
    - - - -
    -
    -
    - Cliente 4 -
    -
    - -
    -
    -
    - Avatar do Cliente -
    -
    - - - -
    -
    -
    - Cliente 4 -
    -
    - -
    -
    -
    - Avatar do Cliente -
    -
    - - - -
    -
    -
    - Cliente 4 -
    -
    - @endsection \ No newline at end of file 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 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..4b62956 --- /dev/null +++ b/resources/views/livewire/admin/add-client.blade.php @@ -0,0 +1,192 @@ +
    + +
    + +
    +
    + +
    + +
    +
    +

    + Adicionar Novo Cliente +

    + +
    +
    + +
    +
    + +
    + +
    +

    Informações Principais

    + +
    +
    + + + @error('form.client_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/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 +