Layout PHP Admin Profissional + Fix UTF-8 do Zero
Você já teve aquele painel PHP feio, com campos grudados, tabela sem espaçamento e letras amontoadas? Pois é, eu também. E uma coisa que aprendi na prática é que um layout ruim não é só questão estética — ele atrapalha o uso, gera erro humano e passa uma imagem amadora do sistema. Neste tutorial vou mostrar exatamente como melhorei um painel de gestão de pizzas em PHP, reescrevendo apenas o CSS e o HTML, sem tocar em uma linha sequer da lógica PHP ou do JavaScript. No final ainda resolvo aquele problema clássico de texto corrompido no banco, tipo "Br├│colis" aparecendo no lugar de "Brócolis".
🎯 Por que melhorar o layout sem mexer na lógica?
Essa é uma das decisões mais inteligentes que você pode tomar num projeto. Imagine que o sistema já está funcionando em produção — cadastrando pizzas, salvando preços, controlando status ativo/inativo. Se você misturar mudanças visuais com mudanças de lógica ao mesmo tempo, qualquer bug que aparecer vai ser um pesadelo para rastrear. Você não vai saber se foi o CSS ou o PHP que quebrou.
A separação entre apresentação e lógica é um princípio básico do desenvolvimento profissional. Em frameworks modernos isso já é nativo, mas em projetos PHP simples (sem framework) você precisa impor essa disciplina manualmente. E é exatamente isso que fizemos aqui.
Se o sistema funciona, você não deve reescrever o PHP. Você melhora o que o usuário vê — e só isso. A lógica que já passou por testes fica intacta.
🔍 O que tinha no código original?
O arquivo original era um painel de admin PHP com as seguintes funcionalidades — todas funcionando corretamente:
- Proteção contra acesso direto (
defined('APP')) - Proteção CSRF com
csrf_require()ecsrf_input() - CRUD completo: criar, atualizar, ativar/desativar e excluir pizzas
- Tabela listando todas as pizzas com preços por tamanho
- Modal de edição via
<dialog>nativo do HTML5 - JavaScript puro para popular o modal com os dados da pizza clicada
O problema era puramente visual: o CSS era genérico, os campos não tinham identidade, a tabela era densa e os botões pareciam elementos de anos 90. Nada de interatividade, nada de feedback visual claro.
🛠️ O que foi mudado — só CSS e HTML
Abaixo listo cada melhoria visual aplicada. Note que nenhuma dessas mudanças altera o comportamento do sistema. Os name dos campos, os id dos elementos, os atributos de formulário — tudo permanece idêntico.
🎨 Tipografia e tokens CSS
Foram introduzidas variáveis CSS (:root) para cores, bordas, sombras e fontes. As fontes escolhidas foram Playfair Display (serifa elegante para títulos) e DM Sans (moderna para corpo de texto) — ambas carregadas via Google Fonts. Isso cria uma identidade visual consistente em toda a página.
:root {
--c-accent: #c0392b; /* vermelho pizza */
--c-green: #27ae60;
--c-bg: #f5f4f0;
--font-head: 'Playfair Display', Georgia, serif;
--font-body: 'DM Sans', system-ui, sans-serif;
}
📬 Flash messages com ícone e animação
As mensagens de feedback (como "Pizza criada") ganharam ícones SVG inline, cores contextuais (verde para sucesso, azul para info, vermelho para exclusão) e uma animação slideDown suave. Antes era apenas um <div class="card"> sem qualquer distinção visual.
.flash { display:flex; align-items:center; gap:.6rem; padding:.85rem 1.25rem;
border-radius:10px; font-weight:500; animation: slideDown .3s ease; }
.flash--success { background:#eafaf1; color:#1a7340; border:1px solid #a9dfbf; }
.flash--danger { background:#fdedec; color:#922b21; border:1px solid #f1948a; }
@keyframes slideDown {
from { opacity:0; transform:translateY(-8px) }
to { opacity:1; transform:none }
}
📋 Formulário organizado com grid responsivo
O formulário de cadastro usava .form-row com grid simples. Mantivemos o grid mas adicionamos labels capitalizados, placeholders, bordas com foco colorido e um divisor visual "Preços por Tamanho" entre os campos de nome/descrição e os campos de preço. O resultado é um formulário que guia o olho do usuário naturalmente.
.pz-group input:focus {
border-color: var(--c-accent);
box-shadow: 0 0 0 3px rgba(192,57,43,.12);
outline: none;
}
📊 Tabela com pills de preço e status
A coluna de preços antes exibia tudo numa string separada por / — difícil de ler. Agora cada tamanho virou um pill individual com label e valor empilhados. O status "Ativa/Inativa" virou um pill colorido com um ponto pulsante verde ou cinza. Muito mais legível de relance.
/* Antes (HTML original): */
R$ 15,00 / 20,00 / 28,00 / 35,00
/* Depois (novo HTML com PHP intacto): */
<span class="price-item">
<span class="price-label">Brot.</span>
<span class="price-val">R$ 15,00</span>
</span>
🗂️ Modal com backdrop blur
O <dialog> original já era bem estruturado. Adicionamos apenas CSS: sombra pronunciada, ::backdrop com blur de fundo e divisão visual entre cabeçalho, corpo e rodapé do modal. Isso dá profundidade e foco sem alterar nenhuma linha de JavaScript.
.pz-modal::backdrop {
background: rgba(26,22,18,.55);
backdrop-filter: blur(3px);
}
📥 Como inserir no seu projeto
O processo é simples. O arquivo gerado é um .php que substitui diretamente o seu arquivo de view. Siga os passos:
- Faça backup do seu arquivo original (nunca pule essa etapa)
- Abra o arquivo novo e confirme que todos os
name=""dos inputs batem com o original - Verifique que os
id=""dos elementos usados no JavaScript estão idênticos (edit_id,edit_nome, etc.) - Substitua o arquivo na sua pasta
views/admin/(ou onde estiver) - Confirme que o Font Awesome já está carregado no seu layout principal (as classes
fas fa-*são usadas) - Teste todos os fluxos: criar, editar, ativar e excluir
Se o seu layout principal já define estilos globais para input, table ou button, pode haver conflito. Nesse caso, prefixe as classes CSS com um namespace (como foi feito com pz-) para isolar o escopo visual deste painel.
🔡 Corrigindo o problema de encoding: Br├│colis → Brócolis
Esse foi o segundo problema identificado. Ao listar as pizzas, textos com acentuação apareciam corrompidos — "Br├│colis", "R├ºcula" — claro sinal de conflito entre o encoding do banco de dados e o da aplicação.
🔎 Por que isso acontece?
O MySQL armazena texto em um encoding específico (UTF-8, Latin-1, etc.). Quando a conexão PHP não especifica o mesmo encoding, os bytes chegam "crus" e o navegador tenta interpretá-los com o charset errado. O resultado são aqueles caracteres estranhos no lugar dos acentos.
✅ Fix para PDO (mais comum)
No arquivo de conexão com o banco (geralmente config/database.php ou similar), garanta que o DSN inclui charset=utf8mb4 e que o atributo de inicialização está definido:
$pdo = new PDO(
'mysql:host=localhost;dbname=SEU_BANCO;charset=utf8mb4',
'usuario',
'senha',
[PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"]
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
✅ Fix para MySQLi
Se o projeto usa mysqli, adicione uma linha logo após criar a conexão:
$conn = new mysqli('host', 'usuario', 'senha', 'banco');
$conn->set_charset('utf8mb4'); // ← essa linha resolve
✅ Meta tag no HTML
Confirme também que o template principal tem a meta tag de charset no <head>:
<meta charset="UTF-8">
O utf8 do MySQL é uma implementação incompleta que suporta apenas 3 bytes por caractere. O utf8mb4 é o verdadeiro UTF-8, com 4 bytes, e suporta emojis e caracteres especiais de todos os idiomas. Sempre prefira utf8mb4 em projetos novos.
👥 Para quem é este tutorial?
Que tem o sistema funcionando mas quer deixar a interface mais profissional sem correr o risco de quebrar o backend.
Que entrega sistemas para clientes e precisa de um painel admin com visual apresentável sem gastar horas em CSS do zero.
Que tem um sistema de cardápio ou catálogo feito por alguém e quer entender como melhorar a usabilidade.
Que quer aprender boas práticas de separação entre lógica e apresentação em projetos reais, não apenas em teoria.
📌 Resumo do que fizemos
| Problema | Solução aplicada | Impacto |
|---|---|---|
| Layout genérico | CSS com tokens, tipografia e paleta de cores | Visual profissional e identidade visual |
| Feedback sem distinção | Flash messages com ícone SVG e animação | Usuário sabe exatamente o que aconteceu |
| Preços ilegíveis na tabela | Pills individuais por tamanho | Leitura rápida de relance |
| Status sem destaque visual | Pill colorido com dot animado | Status identificado instantaneamente |
| Modal sem profundidade | Backdrop blur + sombra + seções | Foco total no modal aberto |
| Texto corrompido (encoding) | charset=utf8mb4 na conexão + SET NAMES |
Acentuação correta em todo o sistema |
ℹ️ Este tutorial é voltado para aprendizado e boas práticas de desenvolvimento. Sempre teste em ambiente de desenvolvimento antes de aplicar em produção. O @CanalQb não se responsabiliza por eventuais impactos em sistemas em uso.
▶️ Acompanhe o @CanalQb no YouTube para mais tutoriais como este!

Comentários
Comente só assim vamos crescer juntos!