295 lines
7.8 KiB
Markdown
295 lines
7.8 KiB
Markdown
# Componentes de Layout Padrão
|
|
|
|
Este diretório contém componentes reutilizáveis para manter um design system consistente em todas as páginas de listagem do dashboard.
|
|
|
|
## Componentes Disponíveis
|
|
|
|
### 1. **PageHeader**
|
|
Header padrão com título, descrição e botão de ação.
|
|
|
|
```tsx
|
|
import PageHeader from '@/components/layout/PageHeader';
|
|
|
|
<PageHeader
|
|
title="Agências"
|
|
description="Gerencie seus parceiros e acompanhe o desempenho."
|
|
actionLabel="Nova Agência"
|
|
onAction={() => setModalOpen(true)}
|
|
/>
|
|
```
|
|
|
|
### 2. **SearchBar**
|
|
Barra de busca padrão com ícone de lupa.
|
|
|
|
```tsx
|
|
import SearchBar from '@/components/layout/SearchBar';
|
|
|
|
<SearchBar
|
|
value={searchTerm}
|
|
onChange={setSearchTerm}
|
|
placeholder="Buscar por nome, email..."
|
|
/>
|
|
```
|
|
|
|
### 3. **StatusFilter**
|
|
Dropdown de filtro de status com Headless UI.
|
|
|
|
```tsx
|
|
import StatusFilter from '@/components/layout/StatusFilter';
|
|
|
|
const STATUS_OPTIONS = [
|
|
{ id: 'all', name: 'Todos os Status' },
|
|
{ id: 'active', name: 'Ativos' },
|
|
{ id: 'inactive', name: 'Inativos' },
|
|
];
|
|
|
|
<StatusFilter
|
|
options={STATUS_OPTIONS}
|
|
selected={selectedStatus}
|
|
onChange={setSelectedStatus}
|
|
/>
|
|
```
|
|
|
|
### 4. **EmptyState**
|
|
Estado vazio padrão com ícone, título e descrição.
|
|
|
|
```tsx
|
|
import EmptyState from '@/components/layout/EmptyState';
|
|
import { BuildingOfficeIcon } from '@heroicons/react/24/outline';
|
|
|
|
<EmptyState
|
|
icon={<BuildingOfficeIcon className="w-8 h-8 text-zinc-400" />}
|
|
title="Nenhuma agência encontrada"
|
|
description="Não encontramos resultados para os filtros selecionados."
|
|
actionLabel="Limpar todos os filtros"
|
|
onAction={clearFilters}
|
|
/>
|
|
```
|
|
|
|
### 5. **LoadingState**
|
|
Estado de carregamento com spinner.
|
|
|
|
```tsx
|
|
import LoadingState from '@/components/layout/LoadingState';
|
|
|
|
{loading && <LoadingState />}
|
|
```
|
|
|
|
### 6. **StatusBadge**
|
|
Badge de status ativo/inativo com toggle opcional.
|
|
|
|
```tsx
|
|
import StatusBadge from '@/components/layout/StatusBadge';
|
|
|
|
<StatusBadge
|
|
active={item.is_active}
|
|
onClick={() => toggleStatus(item.id, item.is_active)}
|
|
activeLabel="Ativo"
|
|
inactiveLabel="Inativo"
|
|
/>
|
|
```
|
|
|
|
### 7. **Pagination** ⭐ NEW
|
|
Paginação funcional com navegação e indicador de páginas.
|
|
|
|
```tsx
|
|
import Pagination from '@/components/layout/Pagination';
|
|
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const itemsPerPage = 10;
|
|
const totalItems = filteredItems.length;
|
|
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
|
|
|
const paginatedItems = filteredItems.slice(
|
|
(currentPage - 1) * itemsPerPage,
|
|
currentPage * itemsPerPage
|
|
);
|
|
|
|
<Pagination
|
|
currentPage={currentPage}
|
|
totalPages={totalPages}
|
|
totalItems={totalItems}
|
|
itemsPerPage={itemsPerPage}
|
|
onPageChange={setCurrentPage}
|
|
/>
|
|
```
|
|
|
|
### 8. **ConfirmDialog** ⭐ NEW
|
|
Modal de confirmação profissional (substitui `confirm()`).
|
|
|
|
```tsx
|
|
import { useState } from 'react';
|
|
import ConfirmDialog from '@/components/layout/ConfirmDialog';
|
|
|
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
const [itemToDelete, setItemToDelete] = useState<string | null>(null);
|
|
|
|
const handleDeleteClick = (id: string) => {
|
|
setItemToDelete(id);
|
|
setConfirmOpen(true);
|
|
};
|
|
|
|
const handleConfirmDelete = () => {
|
|
if (itemToDelete) {
|
|
// Executar exclusão
|
|
deleteItem(itemToDelete);
|
|
}
|
|
};
|
|
|
|
<ConfirmDialog
|
|
isOpen={confirmOpen}
|
|
onClose={() => setConfirmOpen(false)}
|
|
onConfirm={handleConfirmDelete}
|
|
title="Excluir Item"
|
|
message="Tem certeza que deseja excluir este item? Esta ação não pode ser desfeita."
|
|
confirmText="Excluir"
|
|
cancelText="Cancelar"
|
|
variant="danger"
|
|
/>
|
|
```
|
|
|
|
### 9. **ToastContext & useToast** ⭐ NEW
|
|
Sistema de notificações toast (substitui `alert()`).
|
|
|
|
**Setup no layout:**
|
|
```tsx
|
|
import { ToastProvider } from '@/components/layout/ToastContext';
|
|
|
|
<ToastProvider>
|
|
{children}
|
|
</ToastProvider>
|
|
```
|
|
|
|
**Uso nas páginas:**
|
|
```tsx
|
|
import { useToast } from '@/components/layout/ToastContext';
|
|
|
|
const toast = useToast();
|
|
|
|
// Sucesso
|
|
toast.success('Item criado!', 'O item foi criado com sucesso.');
|
|
|
|
// Erro
|
|
toast.error('Erro ao excluir', 'Não foi possível excluir o item.');
|
|
|
|
// Info
|
|
toast.info('Informação', 'Ação concluída.');
|
|
```
|
|
|
|
## Exemplo de Uso Completo
|
|
|
|
```tsx
|
|
"use client";
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import PageHeader from '@/components/layout/PageHeader';
|
|
import SearchBar from '@/components/layout/SearchBar';
|
|
import StatusFilter from '@/components/layout/StatusFilter';
|
|
import LoadingState from '@/components/layout/LoadingState';
|
|
import EmptyState from '@/components/layout/EmptyState';
|
|
import StatusBadge from '@/components/layout/StatusBadge';
|
|
import TableFooter from '@/components/layout/TableFooter';
|
|
import { BuildingOfficeIcon } from '@heroicons/react/24/outline';
|
|
|
|
const STATUS_OPTIONS = [
|
|
{ id: 'all', name: 'Todos os Status' },
|
|
{ id: 'active', name: 'Ativos' },
|
|
{ id: 'inactive', name: 'Inativos' },
|
|
];
|
|
|
|
export default function MyListPage() {
|
|
const [items, setItems] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [selectedStatus, setSelectedStatus] = useState(STATUS_OPTIONS[0]);
|
|
|
|
const filteredItems = items.filter(item => {
|
|
const matchesSearch = item.name.toLowerCase().includes(searchTerm.toLowerCase());
|
|
const matchesStatus =
|
|
selectedStatus.id === 'all' ? true :
|
|
selectedStatus.id === 'active' ? item.is_active :
|
|
!item.is_active;
|
|
return matchesSearch && matchesStatus;
|
|
});
|
|
|
|
return (
|
|
<div className="p-6 max-w-[1600px] mx-auto space-y-6">
|
|
<PageHeader
|
|
title="Minha Lista"
|
|
description="Descrição da página"
|
|
actionLabel="Novo Item"
|
|
onAction={() => {}}
|
|
/>
|
|
|
|
<div className="flex flex-col lg:flex-row gap-4 items-center justify-between">
|
|
<SearchBar
|
|
value={searchTerm}
|
|
onChange={setSearchTerm}
|
|
placeholder="Buscar..."
|
|
/>
|
|
<StatusFilter
|
|
options={STATUS_OPTIONS}
|
|
selected={selectedStatus}
|
|
onChange={setSelectedStatus}
|
|
/>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<LoadingState />
|
|
) : filteredItems.length === 0 ? (
|
|
<EmptyState
|
|
icon={<BuildingOfficeIcon className="w-8 h-8 text-zinc-400" />}
|
|
title="Nenhum item encontrado"
|
|
description="Tente ajustar os filtros."
|
|
/>
|
|
) : (
|
|
<div className="bg-white dark:bg-zinc-900 rounded-xl border border-zinc-200 dark:border-zinc-800 overflow-hidden">
|
|
<table className="w-full">
|
|
{/* Sua tabela aqui */}
|
|
</table>
|
|
<TableFooter count={filteredItems.length} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Design System
|
|
|
|
### Cores
|
|
```css
|
|
--gradient: linear-gradient(135deg, #ff3a05, #ff0080)
|
|
--brand-color: #ff0080
|
|
```
|
|
|
|
### Classes Tailwind Padrão
|
|
|
|
**Container principal:**
|
|
```
|
|
p-6 max-w-[1600px] mx-auto space-y-6
|
|
```
|
|
|
|
**Tabela:**
|
|
```
|
|
bg-white dark:bg-zinc-900 rounded-xl border border-zinc-200 dark:border-zinc-800
|
|
```
|
|
|
|
**Header da tabela:**
|
|
```
|
|
bg-zinc-50/50 dark:bg-zinc-800/50 border-b border-zinc-200 dark:border-zinc-800
|
|
```
|
|
|
|
**Linha hover:**
|
|
```
|
|
hover:bg-zinc-50 dark:hover:bg-zinc-800/50 transition-colors
|
|
```
|
|
|
|
## Benefícios
|
|
|
|
✅ **Consistência visual** - Todas as páginas seguem o mesmo padrão
|
|
✅ **Manutenção fácil** - Altere um componente, atualiza em todas as páginas
|
|
✅ **Desenvolvimento rápido** - Reutilize componentes prontos
|
|
✅ **Design system** - Cores e estilos centralizados
|
|
✅ **Acessibilidade** - Componentes já otimizados
|