Ponto Eletronico
Ponto Eletronico
características:
Requisitos Gerais
Backend em PHP.
Registro de Ponto
Funcionalidades
Painel do Funcionário
Cadastro de Funcionários
Painel Administrativo
Segurança
Visualização e exportação (CSV/Excel) dos registros de ponto por funcionário, setor ou período.
Diferenciação clara entre perfil de Administrador e Funcionário e gerente podendo ser multiempresa.
Relatórios
Extras
│ │ └── style.css
│ ├── /js
│ │ ├── main.js
Código bem comentado e organizado para fácil manutenção. │ │ └── ponto.js
│ └── /assets
│ └── logo.png
├── /painel-admin
Estrutura de um Sistema de Ponto Digital Completo
│ ├── index.php
Aqui está um esboço detalhado para um sistema de ponto digital, abordando todos os seus
│ ├── usuarios.php
requisitos. A estrutura foi pensada para ser modular e escalável, utilizando as tecnologias que você
especificou. │ └── relatorios.php
├── /painel-funcionario
Um sistema bem organizado começa com uma estrutura de arquivos lógica. │ └── historico.php
├── /login.php
/sistema-ponto └── /index.php (página inicial)
├── /backend backend/: Contém toda a lógica PHP, incluindo classes e APIs.
│ ├── /api
│ │ ├── usuario.php
│ │ └── relatorio.php painel-admin/ e painel-funcionario/: Pastas dedicadas aos painéis de cada tipo de usuário.
│ ├── /classes
│ │ ├── Ponto.php
2. Banco de Dados MySQL
│ │ ├── Usuario.php
As tabelas foram projetadas para serem eficientes e relacionais.
│ │ └── AjustePonto.php
│ ├── /includes
Tabela: usuarios
│ │ └── funcoes.php
│ └── index.php
id (INT, PK, AI)
nome (VARCHAR)
ip_registro (VARCHAR)
Tabela: ajustes_ponto
cargo (VARCHAR)
justificativa (TEXT)
Tabela: setores
Tabela: pontos
nome_setor (VARCHAR)
Tabela: empresas
data (DATE)
nome_empresa (VARCHAR)
3. Backend em PHP
Usuario.php: Classe para manipular dados de usuário: cadastro, login, atualização. O método de Layout responsivo usando Flexbox ou CSS Grid.
login deve verificar o hash da senha usando password_verify().
Estilo minimalista e em formato de painel, com menus laterais e cards para informações.
Ponto.php: Classe com métodos para registrar pontos.
Quando um botão é clicado, uma requisição fetch() ou XMLHttpRequest é enviada para a API PHP
Verifica se já existe um registro para o usuário na data atual. (/backend/api/ponto.php).
Insere ou atualiza o registro na tabela pontos. Desabilita o botão clicado para evitar duplicação e exibe uma mensagem de sucesso.
Usa $_SERVER['REMOTE_ADDR'] para capturar o IP. main.js para funcionalidades gerais, como a navegação no painel.
api/: Arquivos PHP que respondem a requisições AJAX do frontend. Por exemplo, api/ponto.php 5. Funcionalidades Essenciais
receberia uma requisição POST com o tipo de ponto a ser batido.
Login:
O PHP verifica as credenciais e, se corretas, inicia uma sessão e redireciona para o painel apropriado
HTML: (/painel-admin ou /painel-funcionario).
login.php: Formulário para e-mail e senha. A sessão deve ter um tempo de expiração (session_set_cookie_params).
painel-admin/index.php: Dashboard com atalhos para gerenciar usuários e relatórios. No painel do funcionário, o JS verifica qual botão (Entrar, Almoço, etc.) deve ser habilitado com base
no último ponto registrado naquele dia.
A requisição para o backend envia o tipo de ponto. O PHP registra a hora e o IP.
Painel do Funcionário:
Histórico: Uma tabela exibe os registros de ponto do usuário, com data e horários. Como você falou em multiempresa e diferentes perfis de usuário, antes de partir pro código, o ideal é
definirmos a estrutura do banco de dados. Isso vai dar a base para o backend e, depois, o frontend se
conecta de forma mais organizada.
Ajuste de Ponto: Um formulário simples para selecionar a data e o tipo de ajuste (entrada, saída) com
um campo de texto para a justificativa. O JS envia os dados para a API de ajustes.
Painel Administrativo:
Estrutura inicial do banco de dados (MySQL)
Relatórios: Um formulário para selecionar filtros (funcionário, setor, período). O JS envia a requisição
para a API de relatórios, que retorna os dados.
Ajustes: Uma tabela lista todos os pedidos de ajuste pendentes. O administrador pode clicar em Guarda os dados de cada empresa cadastrada no sistema.
"Aprovar" ou "Rejeitar", o que atualiza o status na tabela ajustes_ponto.
Relatórios:
Exportação: As APIs de relatório podem gerar o conteúdo de um arquivo CSV, Excel ou PDF. Para PDF,
uma biblioteca PHP leve como o FPDF ou TCPDF pode ser usada. nome VARCHAR(150) NOT NULL,
Este projeto é um excelente ponto de partida. Ele engloba todas as suas necessidades e oferece uma cnpj VARCHAR(20) UNIQUE NOT NULL,
base sólida para a construção de um sistema de ponto robusto e funcional, com o bônus de ser
multiempresa e com diferentes perfis de usuário, como você solicitou. O que você gostaria de
detalhar primeiro: o frontend, o backend ou a estrutura do banco de dados? endereco TEXT,
telefone VARCHAR(20),
email VARCHAR(100),
);
2. Tabela usuarios
);
3. Tabela pontos
cargo VARCHAR(100),
hora TIME NOT NULL,
ip VARCHAR(45),
);
4. Tabela ajustes_ponto
);
Sua análise está corretíssima! Começar pelo banco de dados é a base para garantir que o sistema
seja robusto e escalável. A estrutura que você propôs é excelente e já contempla a lógica
empresa_id INT NOT NULL,
multiempresa e a diferenciação de perfis, o que simplifica muito o desenvolvimento do backend.
A estrutura que você enviou já está bem sólida. Para otimizá-la e evitar problemas futuros, sugiro );
algumas pequenas melhorias e um ajuste de tabela.
Benefícios dessa estrutura:
Tabela pontos
Consultas Simplificadas: Para pegar o histórico de um funcionário, uma única consulta SELECT já
Sua tabela pontos atual registra cada evento (entrada, saida_almoco, etc.) em uma linha separada. retorna todos os horários do dia.
Isso funciona, mas pode complicar o cálculo de horas e a visualização no histórico do funcionário.
Por exemplo, para saber o horário de entrada e saída de um dia específico, você precisaria fazer
várias consultas para o mesmo usuário e data. Prevenção de Duplicatas: A CONSTRAINT unique_ponto garante que não haverá mais de um registro
para o mesmo usuário na mesma data, o que atende ao seu requisito de prevenção de múltiplos
registros duplicados no mesmo dia.
Uma abordagem mais eficiente é ter um único registro por dia para cada funcionário. Isso agrupa os
horários do dia e facilita o cálculo total de horas trabalhadas.
Cálculo de Horas: Fica mais fácil calcular as horas totais do dia (saida_final - entrada) e o tempo de
almoço (retorno_almoco - saida_almoco) diretamente no backend.
Proposta de Melhoria:
IP por Evento: A adição de colunas para o IP de cada evento é uma boa prática para rastreabilidade e
SQL segurança.
id INT AUTO_INCREMENT PRIMARY KEY, Com a sua base e as minhas sugestões, a estrutura final seria a seguinte:
entrada TIME,
CREATE TABLE usuarios ( FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE
nome VARCHAR(150) NOT NULL, A sua estrutura para ajustes_ponto também é ótima, mas podemos simplificar um pouco a coluna
ponto_id. Em vez de referenciar o registro de ponto que será ajustado, ela pode simplesmente
cpf VARCHAR(20) UNIQUE NOT NULL,
guardar os valores "antes" e "depois" do ajuste, para maior clareza. Mas para um sistema inicial, a sua
email VARCHAR(100) UNIQUE NOT NULL, proposta é perfeitamente funcional. Mantenha-a como está.
senha VARCHAR(255) NOT NULL,
entrada TIME, );
saida_almoco TIME, Com esta base, o próximo passo lógico é partir para o backend em PHP. Podemos começar com a
classe de conexão e a classe de usuário, que serão responsáveis pelo registro e login. O que você
retorno_almoco TIME,
acha?
saida_final TIME,
ip_entrada VARCHAR(45),
ip_saida_almoco VARCHAR(45),
ip_retorno_almoco VARCHAR(45),
-- Adicionando colunas de geolocalização
Inclua a localização GPS e/ou identificação de rede wifi / celular usada. latitude_saida_almoco DECIMAL(10, 8),
Você está certo, a inclusão da geolocalização é um ponto crucial para a segurança e a precisão do longitude_retorno_almoco DECIMAL(11, 8),
registro de ponto. Rastrear a localização ou a rede do dispositivo pode prevenir fraudes e garantir que
latitude_saida_final DECIMAL(10, 8),
o funcionário esteja de fato no local de trabalho ao bater o ponto.
longitude_saida_final DECIMAL(11, 8),
rede_wifi_saida_final VARCHAR(255),
A estrutura da tabela pontos ficaria assim:
CONSTRAINT unique_ponto UNIQUE (usuario_id, data),
);
CREATE TABLE pontos (
Como Obter essas Informações no Frontend (JavaScript)
id INT AUTO_INCREMENT PRIMARY KEY,
A captura dessas informações será feita no lado do cliente (o navegador do funcionário) usando
usuario_id INT NOT NULL, JavaScript antes de enviar a requisição para o backend.
data DATE NOT NULL, Localização GPS: Você pode usar a API de Geolocalização do navegador. O código JavaScript é
bastante direto:
entrada TIME,
saida_almoco TIME,
JavaScript
retorno_almoco TIME,
saida_final TIME,
if (navigator.geolocation) {
ip_entrada VARCHAR(45),
navigator.geolocation.getCurrentPosition(
ip_saida_almoco VARCHAR(45),
function(position) {
ip_retorno_almoco VARCHAR(45),
const latitude = position.coords.latitude;
ip_saida_final VARCHAR(45),
const longitude = position.coords.longitude; empresa_id INT NOT NULL,
// Envie a latitude e a longitude para o backend junto com o ponto tipo ENUM('ip_v4', 'ip_v6') NOT NULL,
// Lidar com erros (ex: usuário negou a permissão) FOREIGN KEY (empresa_id) REFERENCES empresas(id) ON DELETE CASCADE
); Frontend (JavaScript): Ao clicar no botão de ponto, o script tenta obter a localização GPS do usuário.
Se conseguir, envia a latitude e longitude via AJAX para o backend.
} else {
A identificação de rede Wi-Fi/celular (SSID da rede Wi-Fi, por exemplo) não é acessível diretamente
pelo navegador por motivos de segurança. A API navigator.connection fornece apenas informações Com essas atualizações, seu sistema de ponto digital se tornará significativamente mais robusto e
genéricas sobre a conexão (tipo de rede, velocidade), mas não o identificador único da rede. seguro. Agora que definimos a estrutura do banco de dados completa, podemos avançar para o
desenvolvimento do backend, começando com a classe de conexão e as funções de autenticação e
registro de usuário, o que acha?
Para contornar isso, uma abordagem comum é a validação pelo IP. O backend pode comparar o IP do
usuário com uma lista de IPs de redes conhecidas da empresa. Essa é uma solução mais segura e
viável para um sistema web.
podemos continuar.
Exemplo de Tabela:
SQL
Ótimo! Agora que a estrutura do banco de dados está completa e robusta, com suporte a
multiempresa, diferentes perfis de usuário, e aprimoramentos para segurança (IP e geolocalização),
CREATE TABLE redes_autorizadas ( vamos construir o backend em PHP.
A classe de conexão é o alicerce do backend. Vamos utilizar PDO (PHP Data Objects), que é a forma } catch (PDOException $e) {
mais segura e moderna de interagir com bancos de dados em PHP. O PDO previne ataques de injeção
// Em um ambiente de produção, logar o erro em vez de exibi-lo
de SQL e oferece uma interface uniforme para diferentes bancos de dados.
die("Erro de Conexão: " . $e->getMessage());
}
Crie um arquivo chamado Conexao.php dentro da pasta /backend/classes/.
}
return self::$conn;
Arquivo: /backend/classes/Conexao.php
}
}
PHP
Pontos-chave:
<?php
Encapsulamento: Usamos propriedades private static para encapsular as credenciais, garantindo
que elas não possam ser acessadas diretamente de fora da classe.
class Conexao {
/**
Lembre-se de substituir os valores localhost, nome_do_seu_banco, usuario_do_banco e
* Conecta-se ao banco de dados usando PDO. sua_senha_secreta pelas suas próprias credenciais.
* @return PDO Retorna o objeto de conexão PDO.
try {
Crie um arquivo chamado Usuario.php dentro da pasta /backend/classes/.
self::$conn = new PDO("mysql:host=" . self::$host . ";dbname=" . self::$dbname, self::$user,
self::$password);
// Define a codificação para UTF-8 para evitar problemas com acentuação PHP
}
<?php
/**
class Usuario { * @return bool Retorna true em caso de sucesso, false em caso de falha.
private $conn; */
public function __construct() { $sql = "INSERT INTO usuarios (empresa_id, nome, cpf, email, senha, cargo, perfil) VALUES
(:empresa_id, :nome, :cpf, :email, :senha, :cargo, :perfil)";
$this->conn = Conexao::getConexao();
$stmt = $this->conn->prepare($sql);
}
*/ $stmt->bindParam(':email', $dados['email']);
$sql = "SELECT * FROM usuarios WHERE email = :email AND ativo = 1"; $stmt->bindParam(':cargo', $dados['cargo']);
$stmt->bindParam(':email', $email);
$usuario = $stmt->fetch(); }
if ($usuario && password_verify($senha, $usuario['senha'])) { // Outros métodos como: buscar usuário por ID, atualizar dados, etc.
return $usuario; }
} Pontos-chave:
return false;
Reutilização: O construtor __construct() chama a classe Conexao para obter a conexão, mostrando a HTML
modularidade do nosso sistema.
<!DOCTYPE html>
Login Seguro: O método login() usa password_verify() para comparar a senha fornecida pelo usuário
<html lang="pt-br">
com o hash armazenado no banco de dados. Isso é a melhor prática de segurança.
<head>
<meta charset="UTF-8">
Cadastro com Hash: O método cadastrar() usa password_hash() com o algoritmo
PASSWORD_BCRYPT para criar um hash seguro da senha antes de armazená-la no banco. Nunca <meta name="viewport" content="width=device-width, initial-scale=1.0">
armazene senhas em texto plano!
<title>Ponto Digital - Login</title>
</head>
O próximo passo é criar o login.php e a API que o frontend usará para se comunicar com o backend. O
<body class="login-page">
que você acha?
<div class="login-container">
<div class="login-box">
<form id="login-form">
<div class="input-group">
<label for="email">E-mail</label>
podemos prosseguir.
<input type="email" id="email" name="email" required>
</div>
<div class="input-group">
Claro, podemos continuar. Agora que temos a base do backend com as classes de conexão e de
usuário, o próximo passo é a criação do frontend para o login e a API que fará a ponte entre o HTML e <label for="senha">Senha</label>
o PHP.
<input type="password" id="senha" name="senha" required>
</div>
Passo 3: O Frontend (Formulário de Login)
<button type="submit" class="btn-primary">Entrar</button>
Vamos criar o formulário de login em HTML e a lógica básica em JavaScript para enviar os dados. Este </form>
arquivo será o login.php, que estará na raiz do projeto.
<div id="message" class="message-container"></div>
</div>
Arquivo: /login.php
</div>
<script src="frontend/js/login.js"></script> method: 'POST',
</body> headers: {
HTML Semântico: O formulário é simples e utiliza divs para agrupar os campos, facilitando a },
estilização com CSS.
body: JSON.stringify({ email: email, senha: senha }),
})
Mensagens de Erro: A div com id="message" será usada para exibir mensagens de sucesso ou erro do
.then(response => response.json())
JavaScript.
.then(data => {
if (data.success) {
CSS e JS: O HTML já faz a chamada para o CSS principal e para o script de login, mantendo a estrutura
organizada. messageDiv.innerHTML = `<i class="fas fa-check-circle"></i> ${data.message}`;
messageDiv.classList.add('success');
Este script irá capturar os dados do formulário e enviá-los para a API PHP sem recarregar a página, setTimeout(() => {
usando fetch().
window.location.href = data.redirect;
}, 1000);
Arquivo: /frontend/js/login.js
} else {
}
document.getElementById('login-form').addEventListener('submit', function(e) {
})
e.preventDefault();
.catch(error => {
});
messageDiv.innerHTML = ''; e.preventDefault(): Impede o comportamento padrão do formulário, que é recarregar a página.
messageDiv.classList.remove('success', 'error');
fetch(): É a API moderna do JavaScript para fazer requisições de rede. Ela envia os dados do
formulário para o backend como um objeto JSON.
fetch('/backend/api/login.php', {
$usuario = new Usuario();
Mensagens Dinâmicas: Com base na resposta da API, o script atualiza a div de mensagem com $autenticado = $usuario->login($email, $senha);
feedback visual para o usuário.
if ($autenticado) {
Passo 5: A API do Backend (Login)
// Inicia a sessão e armazena os dados do usuário
Esta API será o ponto de entrada para a requisição de login. Ela chamará a classe Usuario que
$_SESSION['usuario_id'] = $autenticado['id'];
criamos anteriormente.
$_SESSION['perfil'] = $autenticado['perfil'];
$_SESSION['empresa_id'] = $autenticado['empresa_id'];
Arquivo: /backend/api/login.php
$_SESSION['nome'] = $autenticado['nome'];
$_SESSION['login_time'] = time();
PHP
$redirect_url = '/painel-admin/index.php';
require_once '../classes/Usuario.php';
break;
case 'admin_empresa':
// Obtém o conteúdo do corpo da requisição e decodifica o JSON
case 'gestor':
$input = file_get_contents('php://input');
$redirect_url = '/painel-admin/index.php'; // Ou um painel específico para gestores
$data = json_decode($input, true);
break;
case 'funcionario':
// Verifica se os dados necessários foram enviados
$redirect_url = '/painel-funcionario/index.php';
if (empty($data['email']) || empty($data['senha'])) {
break;
echo json_encode(['success' => false, 'message' => 'E-mail e senha são obrigatórios.']);
default:
exit;
$redirect_url = '/painel-funcionario/index.php';
}
}
$email = $data['email'];
echo json_encode(['success' => true, 'message' => 'Login bem-sucedido!', 'redirect' =>
$senha = $data['senha'];
$redirect_url]);
} else {
// Falha na autenticação
echo json_encode(['success' => false, 'message' => 'E-mail ou senha incorretos.']); Vamos integrar essa funcionalidade no sistema, começando pelo backend, que precisará fornecer o
caminho para a logo da empresa.
}
SQL
Sessões: Após a autenticação, os dados do usuário são salvos em $_SESSION. Isso é crucial para
manter o estado de login e controlar o acesso às outras páginas.
CREATE TABLE empresas (
endereco TEXT,
Com esses arquivos, você terá um sistema de login funcional e seguro. O próximo passo lógico é a
criação de um "middleware" ou uma função de verificação de sessão para proteger as páginas do telefone VARCHAR(20),
painel, garantindo que apenas usuários autenticados possam acessá-las. O que você acha?
email VARCHAR(100),
);
A API de login (login.php) precisa não apenas buscar os dados do usuário, mas também os dados da
empresa associada a ele. Isso nos permitirá enviar o caminho da logo para o frontend.
no login, e em todas as outras paginas inclua uma logo para identificar a empresa do sistema e um
logo para identificar a empresa do funcionario, logo apos ele fazer login.
Arquivo: /backend/api/login.php (atualização)
PHP
case 'admin_sistema':
break;
$redirect_url = '/painel-admin/index.php';
echo json_encode(['success' => false, 'message' => 'E-mail e senha são obrigatórios.']); case 'funcionario':
} break;
default:
$senha = $data['senha']; }
$usuario = new Usuario(); echo json_encode(['success' => true, 'message' => 'Login bem-sucedido!', 'redirect' =>
$redirect_url]);
// Altera o método de login para buscar dados da empresa também
} else {
$autenticado = $usuario->loginComEmpresa($email, $senha);
echo json_encode(['success' => false, 'message' => 'E-mail ou senha incorretos.']);
}
if ($autenticado) {
Passo 3: Atualizando a Classe Usuario
// Inicia a sessão e armazena os dados do usuário e da empresa
Precisamos criar um novo método na classe Usuario para realizar a junção (JOIN) entre as tabelas
$_SESSION['usuario_id'] = $autenticado['id'];
usuarios e empresas e buscar os dados da logo.
$_SESSION['perfil'] = $autenticado['perfil'];
$_SESSION['empresa_id'] = $autenticado['empresa_id'];
Arquivo: /backend/classes/Usuario.php (atualização)
$_SESSION['nome'] = $autenticado['nome'];
$_SESSION['empresa_nome'] = $autenticado['empresa_nome'];
PHP
$_SESSION['empresa_logo'] = $autenticado['empresa_logo_path'];
$_SESSION['login_time'] = time();
// ... (código anterior)
} }
* @param string $senha Agora, em todas as páginas que o usuário acessa após o login (painel do funcionário, painel
administrativo, etc.), você pode exibir os logos da empresa do sistema e da empresa do funcionário.
* @return array|false Retorna os dados do usuário e da empresa se a autenticação for bem-
sucedida, caso contrário, false.
*/ Exemplo: /painel-funcionario/index.php
SELECT
usuarios u <head>
?>
<header class="header">
<div class="logo-container">
<?php
Logos no header: A melhor prática é colocar os logos no cabeçalho de cada página do painel,
garantindo que eles estejam sempre visíveis. session_start();
// Destruir todas as variáveis da sessão
$_SESSION = array(); Vamos criar um arquivo chamado verifica_sessao.php dentro de uma nova pasta
/backend/middlewares/.
if (ini_get("session.use_cookies")) {
PHP
$params = session_get_cookie_params();
);
// Verifica se a sessão do usuário existe
}
if (!isset($_SESSION['usuario_id']) || empty($_SESSION['usuario_id'])) {
}
// Redirecionar para a página de login
header("Location: /login.php");
// Opcional: Controle de tempo de inatividade da sessão
exit;
$tempo_inatividade = 1800; // 30 minutos em segundos
session_start(): Deve ser chamado no início para acessar a sessão existente.
if (isset($_SESSION['login_time']) && (time() - $_SESSION['login_time'] > $tempo_inatividade)) {
session_destroy();
session_destroy(): Destrói a sessão no servidor.
header("Location: /login.php?sessao_expirada=true");
exit;
Redirecionamento: O usuário é enviado de volta para a página de login, onde não há mais uma
sessão ativa. }
Passo 2: O Middleware de Verificação de Sessão // Atualiza o tempo de atividade da sessão a cada requisição
A sua preocupação com a proteção das páginas é totalmente válida. Um "middleware" em PHP pode $_SESSION['login_time'] = time();
ser um arquivo de inclusão que você coloca no topo de cada página restrita. Ele verifica a existência
da sessão e, se ela não for válida, redireciona o usuário para a página de login.
Verificação Inicial: A primeira verificação (!isset($_SESSION['usuario_id'])) é a mais importante. Ela <?php if (!empty($_SESSION['empresa_logo'])): ?>
garante que a variável de sessão que define o login (usuario_id) realmente existe.
<img src="/<?php echo htmlspecialchars($_SESSION['empresa_logo']); ?>" alt="Logo da
Empresa" class="logo-empresa">
Controle de Tempo de Inatividade: A segunda parte é um requisito de segurança adicional. Ela verifica <?php endif; ?>
se o usuário ficou inativo por um período definido (30 minutos, por exemplo) e, se sim, destrói a
</div>
sessão. Isso impede que uma sessão ativa fique exposta em um computador público, por exemplo.
<div class="user-info">
</div>
Exemplo: /painel-funcionario/index.php (atualização)
</header>
PHP
<div class="dashboard-content">
</div>
<?php
</body>
// Incluir o middleware de verificação de sessão no topo
</html>
require_once __DIR__ . '/../backend/middlewares/verifica_sessao.php';
require_once __DIR__ . '/../backend/middlewares/verifica_sessao.php';: O uso de __DIR__ garante que
?> o caminho seja absoluto, evitando erros de inclusão.
<!DOCTYPE html>
<html lang="pt-br"> Com o logout.php e o verifica_sessao.php, o sistema agora tem uma base de segurança sólida para a
autenticação e o controle de acesso.
<head>
<meta charset="UTF-8">
O próximo passo é construir a página principal do funcionário (/painel-funcionario/index.php),
<meta name="viewport" content="width=device-width, initial-scale=1.0">
incluindo os botões de ponto e a lógica JavaScript para registrar os eventos. O que você acha?
<title>Painel do Funcionário</title>
</head>
<body>
<header class="header">
Prosseguir.
<div class="logo-container">
Ótimo. Com a segurança de acesso e a estrutura de sessão prontas, podemos focar na <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-
funcionalidade principal do sistema: o registro de ponto. beta3/css/all.min.css">
</head>
Vamos criar o painel do funcionário, que terá os botões para bater o ponto e a lógica JavaScript para <body>
enviar os dados para o backend, incluindo as informações de geolocalização.
<header class="header">
Passo 1: A Lógica do Frontend (HTML e JavaScript)
<div class="logo-container">
Primeiro, vamos construir o arquivo /painel-funcionario/index.php. Ele será o painel principal, com a
<img src="/frontend/assets/logo-sistema.png" alt="Logo do Sistema" class="logo-sistema">
interface para o funcionário.
<?php if (!empty($_SESSION['empresa_logo'])): ?>
<div class="user-info">
<?php
<span class="user-name">Olá, <?php echo htmlspecialchars($_SESSION['nome']); ?></span>
// Middleware de verificação de sessão
<a href="/logout.php" class="btn-logout"><i class="fas fa-sign-out-alt"></i> Sair</a>
require_once __DIR__ . '/../backend/middlewares/verifica_sessao.php';
</div>
</header>
// Verifica se o perfil é 'funcionario' ou 'gestor'. Outros perfis podem ser redirecionados.
</div> * @param string $tipo_ponto Tipo de ponto a ser batido ('entrada', 'saida_almoco', etc.).
<script src="../frontend/js/ponto.js"></script> * @return bool Retorna true em caso de sucesso, false em caso de falha.
</body> */
Estrutura: O HTML é simples, contendo os botões de ponto com IDs que usaremos no JavaScript. $data_hoje = date('Y-m-d');
$hora_agora = date('H:i:s');
Estado Inicial: Os botões de almoço e saída final começam desabilitados (disabled), pois só podem $ip_ponto = $localizacao['ip'] ?? '0.0.0.0';
ser ativados após o registro do ponto de entrada.
$latitude = $localizacao['latitude'] ?? null;
Agora, vamos criar a classe que irá interagir com a tabela pontos do banco de dados.
// Verifica se já existe um registro para o dia
$sql = "SELECT id FROM pontos WHERE usuario_id = :usuario_id AND data = :data";
Arquivo: /backend/classes/Ponto.php
$stmt = $this->conn->prepare($sql);
$stmt->bindParam(':usuario_id', $usuario_id);
PHP
$stmt->bindParam(':data', $data_hoje);
$stmt->execute();
<?php
$ponto_do_dia = $stmt->fetch();
require_once 'Conexao.php';
if ($ponto_do_dia) {
class Ponto {
// Se o registro já existe, atualiza o campo correto
private $conn;
$sql_update = "UPDATE pontos SET {$tipo_ponto} = :hora, ip_{$tipo_ponto} = :ip,
latitude_{$tipo_ponto} = :latitude, longitude_{$tipo_ponto} = :longitude WHERE id = :id";
} $stmt_update->bindParam(':ip', $ip_ponto);
$stmt_update->bindParam(':latitude', $latitude);
} else { PHP
$sql_insert = "INSERT INTO pontos (usuario_id, empresa_id, data, entrada, ip_entrada, session_start();
latitude_entrada, longitude_entrada) VALUES (:usuario_id, :empresa_id, :data, :hora, :ip, :latitude,
header('Content-Type: application/json');
:longitude)";
$stmt_insert = $this->conn->prepare($sql_insert);
require_once '../classes/Ponto.php';
$stmt_insert->bindParam(':usuario_id', $usuario_id);
$stmt_insert->bindParam(':empresa_id', $empresa_id);
// Verifica se o usuário está logado
$stmt_insert->bindParam(':data', $data_hoje);
if (!isset($_SESSION['usuario_id'])) {
$stmt_insert->bindParam(':hora', $hora_agora);
echo json_encode(['success' => false, 'message' => 'Sessão expirada.']);
$stmt_insert->bindParam(':ip', $ip_ponto);
exit;
$stmt_insert->bindParam(':latitude', $latitude);
}
$stmt_insert->bindParam(':longitude', $longitude);
return $stmt_insert->execute();
$usuario_id = $_SESSION['usuario_id'];
}
$empresa_id = $_SESSION['empresa_id'];
// Não permite criar registro se não for o ponto de entrada
return false;
$input = file_get_contents('php://input');
}
$data = json_decode($input, true);
}
}
if (empty($data['tipo_ponto'])) {
Lógica de Registro/Atualização: O método registrarPonto() verifica se já existe um registro para o dia.
Se sim, ele atualiza a coluna correspondente (entrada, saida_almoco, etc.). Se não, ele só permite a echo json_encode(['success' => false, 'message' => 'Tipo de ponto inválido.']);
criação de um novo registro se o tipo de ponto for entrada.
exit;
}
Segurança: O uso de bindParam com instruções SQL preparadas evita a injeção de SQL.
$tipo_ponto = $data['tipo_ponto'];
Passo 3: A API de Ponto do Backend
$localizacao = [
Esta API receberá a requisição do frontend e chamará a classe Ponto para registrar o evento.
'ip' => $_SERVER['REMOTE_ADDR'],
navigator.geolocation.getCurrentPosition(
echo json_encode(['success' => true, 'message' => 'Ponto registrado com sucesso!']); },
echo json_encode(['success' => false, 'message' => 'Não foi possível registrar o ponto.']); console.error('Erro na geolocalização:', error.message);
Validação de Sessão: A API garante que somente usuários autenticados possam registrar o ponto. messageDiv.innerHTML = '<i class="fas fa-exclamation-triangle"></i> Localização não
obtida. Registrando ponto sem GPS.';
messageDiv.classList.add('warning');
Recebendo Dados: A API recebe o tipo de ponto e a localização do JavaScript e os passa para a
classe Ponto. sendPonto(tipo_ponto);
Este é o script mais importante, pois ele coordena a interface e a comunicação com a API. } else {
messageDiv.classList.add('warning');
JavaScript
sendPonto(tipo_ponto);
}
document.addEventListener('DOMContentLoaded', function() {
});
const buttons = document.querySelectorAll('.btn-ponto');
});
const messageDiv = document.getElementById('ponto-message');
'Content-Type': 'application/json',
// Tenta obter a localização GPS
}, .catch(error => {
document.getElementById('entrada').disabled = true;
// Habilitar o próximo botão na sequência
document.getElementById('saida_almoco').disabled = false;
if (tipo_ponto === 'entrada') {
}
document.getElementById('saida_almoco').disabled = false;
if (data.status.saida_almoco) {
} else if (tipo_ponto === 'saida_almoco') {
document.getElementById('saida_almoco').disabled = true;
document.getElementById('retorno_almoco').disabled = false;
document.getElementById('retorno_almoco').disabled = false;
} else if (tipo_ponto === 'retorno_almoco') {
}
document.getElementById('saida_final').disabled = false;
if (data.status.retorno_almoco) {
}
document.getElementById('retorno_almoco').disabled = true;
document.getElementById('saida_final').disabled = false;
} else {
}
messageDiv.innerHTML = `<i class="fas fa-exclamation-circle"></i> ${data.message}`;
if (data.status.saida_final) {
messageDiv.classList.add('error');
document.getElementById('saida_final').disabled = true;
messageDiv.classList.remove('success', 'warning');
}
}
}
})
})
.catch(error => console.error('Erro ao verificar status do ponto:', error)); Passo 1: A Lógica do Backend (Classe Relatorio)
} Primeiro, precisamos de uma classe para lidar com todas as operações de relatórios e busca de
dados, incluindo os registros de ponto dos funcionários. Crie um arquivo chamado Relatorio.php
dentro da pasta /backend/classes/.
// Executa a função na carga da página
checkPontoStatus();
Arquivo: /backend/classes/Relatorio.php
});
PHP
Geolocalização: O JavaScript tenta obter a localização. Se o usuário negar a permissão, ele envia a
requisição sem os dados de GPS, mas com um aviso.
<?php
require_once 'Conexao.php';
Sequência de Botões: O código do JavaScript desabilita o botão clicado e habilita o próximo na
sequência (entrada -> saida_almoco, etc.).
class Relatorio {
Estado do Dia: A função checkPontoStatus() (que precisa de uma API de backend correspondente) é private $conn;
crucial para manter o estado dos botões mesmo após o usuário sair da página e voltar.
/**
*/
FROM */
JOIN $sql = "SELECT id, nome, cargo FROM usuarios WHERE empresa_id = :empresa_id AND perfil !=
'admin_sistema' ORDER BY nome ASC";
usuarios u ON p.usuario_id = u.id
$stmt = $this->conn->prepare($sql);
WHERE
$stmt->bindParam(':empresa_id', $empresa_id);
p.empresa_id = :empresa_id
$stmt->execute();
";
return $stmt->fetchAll(PDO::FETCH_ASSOC);
$params = [':empresa_id' => $empresa_id];
}
}
if ($usuario_id) {
Consultas Dinâmicas: O método getRegistrosPonto utiliza uma abordagem flexível. Ele constrói a
$sql .= " AND p.usuario_id = :usuario_id";
consulta SQL dinamicamente, adicionando cláusulas WHERE com base nos filtros fornecidos
$params[':usuario_id'] = $usuario_id; (usuario_id, data_inicio, data_fim). Isso otimiza a busca no banco de dados.
}
Segurança com Parâmetros: O uso de execute($params) é crucial para prevenir injeções de SQL,
mesmo com a construção dinâmica da consulta.
if ($data_inicio && $data_fim) {
}
Arquivo: /painel-admin/index.php
$stmt = $this->conn->prepare($sql);
<?php
$stmt->execute($params);
require_once __DIR__ . '/../backend/middlewares/verifica_sessao.php';
return $stmt->fetchAll(PDO::FETCH_ASSOC);
require_once __DIR__ . '/../backend/classes/Relatorio.php';
}
$funcionarios = $relatorio->getFuncionariosDaEmpresa($empresa_id);
<div class="dashboard-content">
</head>
<header class="header">
<th>Entrada</th> Agora, vamos criar a API que o JavaScript do painel administrativo usará para buscar os dados de
forma assíncrona.
<th>Almoço</th>
<th>Retorno</th>
Arquivo: /backend/api/relatorio.php
<th>Saída Final</th>
<th>Horas Totais</th>
PHP
</tr>
</thead>
<?php
<tbody>
session_start();
<?php
header('Content-Type: application/json');
// Exemplo de como os dados seriam renderizados
</tbody>
// Verifica se o usuário tem permissão
</table>
if (!isset($_SESSION['perfil']) || $_SESSION['perfil'] === 'funcionario') {
</div>
echo json_encode(['success' => false, 'message' => 'Acesso negado.']);
exit;
<button id="export-csv" class="btn-secondary">Exportar CSV</button>
}
</div>
$empresa_id = $_SESSION['empresa_id'];
<script src="../frontend/js/admin.js"></script>
$relatorio = new Relatorio();
</body>
</html>
// Recebe os filtros do frontend
Verificação de Perfil: O middleware garante que apenas usuários com o perfil correto
(admin_empresa, gestor, admin_sistema) possam acessar a página. $usuario_id = $_GET['usuario_id'] ?? null;
Filtros de Relatório: O formulário com select e inputs de data permite que o administrador refine a $data_fim = $_GET['data_fim'] ?? null;
busca.
Resposta JSON: A API retorna os dados formatados em JSON, que o JavaScript no frontend irá
$entrada = new DateTime($ponto['entrada']); consumir para exibir a tabela.
$saida_final = new DateTime($ponto['saida_final']);
}
// Função para buscar e renderizar os dados
function fetchAndRenderData() {
return number_format($horas_trabalhadas, 2) . 'h';
const usuarioId = document.getElementById('filtro-funcionario').value;
}
const dataInicio = document.getElementById('filtro-data-inicio').value;
}, $registros); fetch(url)
renderTable(data.data); }
} else {
} filtroForm.addEventListener('submit', function(e) {
}) e.preventDefault();
} fetchAndRenderData();
});
function renderTable(registros) { fetchAndRenderData: Esta função é a peça central. Ela coleta os valores dos filtros, monta a URL da
API e, após a resposta, chama a renderTable para atualizar a interface.
tabelaBody.innerHTML = ''; // Limpa a tabela
if (registros.length === 0) {
Manipulação do DOM: O JavaScript usa métodos nativos como createElement e appendChild para
tabelaBody.innerHTML = '<tr><td colspan="7">Nenhum registro encontrado.</td></tr>';
construir a tabela de forma dinâmica, garantindo um layout simples e eficiente.
return;
}
Com essa implementação, o painel administrativo já possui a funcionalidade de listagem e filtragem
de registros. O próximo passo será adicionar o sistema de solicitação de ajuste de ponto para os
funcionários e a funcionalidade de aprovação/rejeição para os administradores. O que você acha?
registros.forEach(registro => {
row.innerHTML = `
<td>${registro.nome_funcionario}</td>
<td>${registro.data}</td>
<td>${registro.entrada || '---'}</td>
<td>${registro.saida_almoco || '---'}</td>
prosseguir.
<td>${registro.retorno_almoco || '---'}</td>
<td>${registro.saida_final || '---'}</td>
<td>${registro.horas_totais}</td>
Excelente. Vamos agora implementar as funcionalidades de solicitação e gestão de ajustes de ponto.
`;
Esta etapa é crucial para a flexibilidade do sistema, permitindo que os funcionários corrijam erros e
tabelaBody.appendChild(row); que os gestores mantenham a precisão dos registros.
* @param string $justificativa
Vamos dividir o trabalho em três partes: a classe do backend para lidar com os ajustes, o frontend do * @return bool
funcionário para enviar as solicitações, e o frontend do administrador para aprovar ou rejeitar.
*/
Vamos criar uma nova classe para lidar com as operações na tabela ajustes_ponto. Ela terá métodos $sql = "INSERT INTO ajustes_ponto (usuario_id, empresa_id, data, tipo_ajuste, hora_ajustada,
para criar uma nova solicitação e para atualizar o status de uma solicitação existente. justificativa) VALUES (:usuario_id, :empresa_id, :data, :tipo_ajuste, :hora_ajustada, :justificativa)";
$stmt = $this->conn->prepare($sql);
$stmt->bindParam(':empresa_id', $empresa_id);
$stmt->bindParam(':tipo_ajuste', $tipo_ajuste);
$stmt->bindParam(':justificativa', $justificativa);
require_once 'Conexao.php'; }
} */
/** $sql = "UPDATE ajustes_ponto SET status = :status, updated_at = NOW() WHERE id = :id";
*/ $historico_ponto = $relatorio->getRegistrosPonto($_SESSION['empresa_id'],
$_SESSION['usuario_id']);
public function getAjustes($empresa_id, $status = null) {
?>
$sql = "SELECT aj.*, u.nome AS nome_funcionario FROM ajustes_ponto aj JOIN usuarios u ON
aj.usuario_id = u.id WHERE aj.empresa_id = :empresa_id";
<html lang="pt-br">
if ($status) { <head>
<div class="user-info">
HTML
<span class="user-name">Olá, <?php echo htmlspecialchars($_SESSION['nome']); ?></span>
<th>Retorno</th>
<div id="modal-ajuste" class="modal">
<th>Saída Final</th>
<div class="modal-content">
<th>Horas</th>
<span class="close-btn">×</span>
<th>Ação</th>
<h3>Solicitar Ajuste de Ponto</h3>
</tr>
<form id="form-ajuste">
</thead>
<input type="hidden" id="ajuste-data" name="data">
<tbody>
<div class="input-group">
<?php foreach ($historico_ponto as $ponto): ?>
<label for="tipo_ajuste">Tipo de Ajuste</label>
<tr>
<select id="tipo_ajuste" name="tipo_ajuste" required>
<td><?php echo date('d/m/Y', strtotime($ponto['data'])); ?></td>
<option value="entrada">Entrada</option>
<td><?php echo $ponto['entrada'] ?? '---'; ?></td>
<option value="saida_almoco">Saída Almoço</option>
<td><?php echo $ponto['saida_almoco'] ?? '---'; ?></td>
<option value="retorno_almoco">Retorno Almoço</option>
<td><?php echo $ponto['retorno_almoco'] ?? '---'; ?></td>
<option value="saida_final">Saída Final</option>
<td><?php echo $ponto['saida_final'] ?? '---'; ?></td>
</select>
<td><?php
</div>
$p = new Ponto();
<div class="input-group">
echo $p->calcularHoras($ponto);
<label for="hora_ajustada">Hora Correta</label>
?></td>
<input type="time" id="hora_ajustada" name="hora_ajustada" required>
<td>
</div>
<div class="input-group"> }
<label for="justificativa">Justificativa</label>
}
<script src="../frontend/js/ajuste.js"></script>
</body>
$ajuste = new AjustePonto();
</html>
$resultado = $ajuste->solicitarAjuste(
Modal: Um modal simples (div.modal) com um formulário será usado para coletar os dados do
ajuste. $_SESSION['usuario_id'],
$_SESSION['empresa_id'],
Esta API receberá os dados do formulário do funcionário e chamará a classe AjustePonto. $data['tipo_ajuste'],
$data['hora_ajustada'],
);
PHP
if ($resultado) {
<?php echo json_encode(['success' => true, 'message' => 'Solicitação de ajuste enviada com sucesso!']);
session_start(); } else {
header('Content-Type: application/json'); echo json_encode(['success' => false, 'message' => 'Erro ao enviar a solicitação.']);
Arquivo: /frontend/js/ajuste.js
if (!isset($_SESSION['usuario_id']) || empty($_SESSION['usuario_id'])) {
exit;
document.addEventListener('DOMContentLoaded', function() { justificativa: document.getElementById('justificativa').value
headers: {
button.addEventListener('click', function() { },
ajusteDataInput.value = dataPonto; })
}); alert(data.message);
if (data.success) {
}); window.location.reload();
window.addEventListener('click', function(event) { })
}); });
});
const formData = {
tipo_ajuste: document.getElementById('tipo_ajuste').value,
<th>Funcionário</th>
exit; <th>Justificativa</th>
} <th>Status</th>
<th>Ação</th>
?> <tbody>
</button>
if (!isset($_SESSION['perfil']) || ($_SESSION['perfil'] !== 'admin_empresa' && $_SESSION['perfil'] !==
</td> 'gestor' && $_SESSION['perfil'] !== 'admin_sistema')) {
</tbody>
</div>
</html> exit;
Botões: Os botões "Aprovar" e "Rejeitar" possuem um data-id que armazena o ID da solicitação para $ajuste_id = $data['id'];
ser enviado à API.
$novo_status = $data['status'];
Arquivo: /backend/api/gerenciar_ajuste.php
if ($resultado) {
$dados_ajuste = $item;
break; const ajusteId = btn.getAttribute('data-id');
} gerenciarAjuste(ajusteId, 'aprovado');
} }
// Simula uma chamada de registro de ponto para atualizar o registro existente const ajusteId = btn.getAttribute('data-id');
$dados_ajuste['usuario_id'], }
$dados_ajuste['empresa_id'], });
$dados_ajuste['tipo_ajuste'],
['ip' => 'ajuste-admin', 'latitude' => null, 'longitude' => null] function gerenciarAjuste(ajusteId, status) {
); if (!confirm(`Tem certeza que deseja ${status === 'aprovado' ? 'APROVAR' : 'REJEITAR'} esta
solicitação?`)) {
}
return;
}
}
echo json_encode(['success' => true, 'message' => 'Solicitação de ajuste atualizada com
sucesso.']);
} else { fetch('/backend/api/gerenciar_ajuste.php', {
echo json_encode(['success' => false, 'message' => 'Erro ao atualizar a solicitação.']); method: 'POST',
} headers: {
Arquivo: /frontend/js/ajuste_admin.js },
body: JSON.stringify({
status: status
window.location.reload(); Vamos adicionar essa funcionalidade ao sistema, começando com a estrutura do backend para gerar
os relatórios e enviar os e-mails, e depois integrando a interface no painel administrativo.
}
})
Passo 1: A Lógica do Backend para Exportação e E-mail
.catch(error => {
Para manter o código organizado, vamos criar um novo arquivo para lidar especificamente com as
console.error('Erro:', error);
operações de exportação.
alert('Erro ao processar a solicitação.');
});
Arquivo: /backend/api/exportar_relatorio.php
}
});
Este arquivo será a API que recebe as solicitações de exportação do frontend, gera o relatório no
Com essas novas funcionalidades, o sistema de ponto agora tem um ciclo completo: registro, formato desejado e o envia por e-mail ou o prepara para download.
solicitação de ajuste e gestão dos ajustes.
PHP
O próximo passo é a exportação de relatórios (CSV/Excel e PDF) e a finalização das funcionalidades
do painel administrativo, como a gestão de usuários. O que você acha de continuarmos com a
exportação de relatórios? <?php
session_start();
require_once '../middlewares/verifica_sessao.php';
require_once '../classes/Relatorio.php';
http_response_code(403);
inclua exportar tb para email do funcionario. echo json_encode(['success' => false, 'message' => 'Acesso negado.']);
exit;
use sendmail do php, mas deixe opção em formulario apra usar um host smtp. }
$emailConfig = [
Excelente. A capacidade de exportar relatórios é um diferencial importante, e a inclusão do envio por 'metodo' => 'smtp', // ou 'sendmail'
e-mail com opções de configuração a torna ainda mais poderosa e flexível.
'smtp_host' => 'smtp.seuservidor.com',
'smtp_port' => 587,
// Funções de exportação (pode ser em uma classe separada para melhor organização) $pdf->SetFont('Arial', '', 12);
ob_start(); // ...
$output = fopen("php://output", 'w'); return $pdf->Output('S'); // Retorna o conteúdo do PDF como string
$cabecalho = array('Funcionario', 'Data', 'Entrada', 'Almoco', 'Retorno', 'Saida Final', 'Horas Totais'); }
foreach ($registros as $row) { function enviarEmail($para, $assunto, $corpo, $anexo, $nomeAnexo, $config) {
$row['nome_funcionario'], // Usando a função mail() do PHP (requer servidor configurado com sendmail)
$row['retorno_almoco'],
if ($anexo) {
} $dados_com_horas = array_map(function($ponto) {
return $ponto;
// Implementação básica de SMTP (recomendado usar PHPMailer ou similar) $formato = $_GET['formato'] ?? 'csv';
// Este é um exemplo, para um sistema de produção, use uma biblioteca. $destino = $_GET['destino'] ?? 'download'; // 'download' ou 'email'
} }
<div class="input-group">
// A função calcularHoras deve ser a mesma da API de relatorio.php
<label for="export-destino">Destino:</label>
function calcularHoras($ponto) {
<select id="export-destino" name="destino">
// ... código de cálculo de horas ...
<option value="download">Download</option>
}
<option value="email">Enviar por E-mail</option>
Observação de Segurança: As credenciais SMTP NUNCA devem ser expostas no frontend.
</select>
Recomenda-se armazená-las em um arquivo de configuração fora da raiz pública do servidor e incluí-
lo no backend. O formulário do frontend apenas pergunta se o usuário deseja usar SMTP e o backend </div>
já acessa as credenciais.
</form>
Adicione o formulário e os botões de exportação. Pode ser um modal ou uma seção expandida.
</div>
</div>
HTML
<script src="../frontend/js/admin.js"></script>
<div class="dashboard-content">
Passo 3: A Lógica do Frontend (JavaScript)
O script JS precisa agora lidar com o novo formulário de exportação. const usuarioId = document.getElementById('filtro-funcionario').value;
if (email) {
if (usuarioId) {
url += `&data_inicio=${dataInicio}`;
emailFieldContainer.style.display = 'block'; }
document.getElementById('export-email').setAttribute('required', '');
} fetch(url)
.then(data => {
exportForm.addEventListener('submit', function(e) { })
}); <?php
Com esta implementação, o painel administrativo agora tem a capacidade de gerar relatórios em require_once 'Conexao.php';
diferentes formatos e enviá-los por e-mail, atendendo a todos os seus requisitos.
class GerenciamentoUsuario {
O próximo passo é finalizar o projeto, incluindo a gestão de usuários no painel administrativo, onde
private $conn;
será possível cadastrar, editar e desativar funcionários. O que você acha de continuarmos por aí?
$this->conn = Conexao::getConexao();
/**
* @param array $dados Dados do usuário (nome, email, senha, perfil, etc.).
prosseguir.
* @return bool
*/
$stmt = $this->conn->prepare($sql);
Vamos dividir a implementação em três partes: a classe do backend para gerenciar os usuários, a
interface do painel administrativo e as APIs correspondentes.
$senha_hash = password_hash($dados['senha'], PASSWORD_DEFAULT);
$stmt->bindParam(':perfil', $dados['perfil']);
Crie um arquivo chamado GerenciamentoUsuario.php na pasta /backend/classes/.
$stmt->bindParam(':empresa_id', $dados['empresa_id']);
Arquivo: /backend/classes/GerenciamentoUsuario.php
return $stmt->execute();
}
PHP
/**
*/ $sql = "SELECT id, nome, email, perfil, ativo FROM usuarios WHERE empresa_id = :empresa_id
ORDER BY nome ASC";
public function editarUsuario($id, $dados) {
$stmt = $this->conn->prepare($sql);
$sql = "UPDATE usuarios SET nome = :nome, email = :email, perfil = :perfil WHERE id = :id";
$stmt->bindParam(':empresa_id', $empresa_id);
$stmt = $this->conn->prepare($sql);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt->bindParam(':nome', $dados['nome']);
}
$stmt->bindParam(':email', $dados['email']);
$stmt->bindParam(':perfil', $dados['perfil']);
/**
$stmt->bindParam(':id', $id);
* Busca um único usuário pelo ID.
}
Passo 2: O Frontend do Painel Administrativo
Vamos criar a página de gestão de usuários. A partir dela, o administrador poderá ver a lista de
usuários e acessar os formulários de adição/edição.
<header class="header">
<div class="logo-container">
Arquivo: /painel-admin/usuarios.php (nova página)
<img src="/frontend/assets/logo-sistema.png" alt="Logo do Sistema" class="logo-sistema">
exit; </header>
<div class="dashboard-content">
<table>
<!DOCTYPE html>
<thead>
<html lang="pt-br">
<tr>
<head>
<th>Nome</th>
<meta charset="UTF-8">
<th>E-mail</th>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<th>Perfil</th>
<title>Ponto Digital - Gerenciar Usuários</title>
<th>Status</th>
<link rel="stylesheet" href="../frontend/css/style.css">
<th>Ações</th>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-
beta3/css/all.min.css"> </tr>
</head> </thead>
<tr> </div>
<label for="nome">Nome:</label>
Arquivo: /backend/api/gerenciar_usuarios.php (nova página)
<input type="text" id="nome" name="nome" required>
</div>
PHP
<div class="input-group">
<label for="email">E-mail:</label>
<?php
session_start(); }
exit; echo json_encode(['success' => true, 'message' => 'Usuário editado com sucesso.']);
} } else {
} else {
$acao = $_POST['acao'] ?? $_GET['acao'] ?? ''; echo json_encode(['success' => false, 'message' => 'ID de usuário inválido.']);
case 'listar':
echo json_encode(['success' => true, 'data' => $usuarios]); $id = $_POST['id'] ?? null;
break; if ($id) {
$resultado = $gerenciamentoUsuario->desativarUsuario($id);
$dados = $_POST; echo json_encode(['success' => true, 'message' => 'Usuário desativado com sucesso.']);
$resultado = $gerenciamentoUsuario->adicionarUsuario($dados); echo json_encode(['success' => false, 'message' => 'Erro ao desativar usuário.']);
if ($resultado) { }
echo json_encode(['success' => true, 'message' => 'Usuário adicionado com sucesso.']); } else {
} else { http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Erro ao adicionar usuário.']); echo json_encode(['success' => false, 'message' => 'ID de usuário inválido.']);
} document.addEventListener('DOMContentLoaded', function() {
echo json_encode(['success' => true, 'data' => $usuario]); const tabelaBody = document.getElementById('usuarios-table-body');
} else {
echo json_encode(['success' => false, 'message' => 'Usuário não encontrado.']); // Abre o modal para adicionar usuário
} addUsuarioBtn.addEventListener('click', function() {
http_response_code(400); usuarioForm.reset();
echo json_encode(['success' => false, 'message' => 'ID de usuário inválido.']); usuarioIdInput.value = '';
} senhaGroup.style.display = 'block';
usuarioModal.style.display = 'block';
default: });
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Ação inválida.']); // Fecha o modal
} usuarioModal.style.display = 'none';
Este script irá controlar a interface e interagir com a nova API para gerenciar os usuários de forma
dinâmica.
window.addEventListener('click', function(event) {
if (event.target == usuarioModal) {
Arquivo: /frontend/js/usuarios_admin.js
usuarioModal.style.display = 'none';
}
JavaScript
});
// Submissão do formulário const id = target.getAttribute('data-id');
usuarioForm.addEventListener('submit', function(e) {
e.preventDefault(); if (target.classList.contains('edit-btn')) {
fetch(`/backend/api/gerenciar_usuarios.php?acao=get&id=${id}`)
}) document.getElementById('email').value = user.email;
.then(data => {
if (data.success) { document.getElementById('senha').removeAttribute('required');
window.location.reload(); } else {
} alert(data.message);
}) }
console.error('Erro:', error); }
}); if (target.classList.contains('desativar-btn')) {
method: 'POST',
body: formData,
.then(data => { O sistema parece estar finalizado e pronto para ser implementado. O que você acha? Podemos
considerar o projeto concluíd
alert(data.message);
if (data.success) {
window.location.reload();
})
.catch(error => {
console.error('Erro:', error);
});
});
});
Com esta etapa, o sistema de gestão de usuários está completo. Os administradores podem
adicionar, editar e desativar contas de forma segura.