Files
aggios.app/front-end-dash.aggios.app/components/layout/README.md
2025-12-17 13:36:23 -03:00

7.8 KiB

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.

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)}
/>

Barra de busca padrão com ícone de lupa.

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.

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.

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.

import LoadingState from '@/components/layout/LoadingState';

{loading && <LoadingState />}

6. StatusBadge

Badge de status ativo/inativo com toggle opcional.

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.

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()).

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:

import { ToastProvider } from '@/components/layout/ToastContext';

<ToastProvider>
    {children}
</ToastProvider>

Uso nas páginas:

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

"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

--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