chore(release): snapshot 1.4.2
This commit is contained in:
294
front-end-dash.aggios.app/components/layout/README.md
Normal file
294
front-end-dash.aggios.app/components/layout/README.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user