commit
fea72ce60f
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-empty-object-type": "off"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# DRE Gerencial - Documentação do Sistema
|
||||
|
||||
## Visão Geral
|
||||
|
||||
O **DRE Gerencial** é um sistema web desenvolvido em Next.js para análise e visualização de dados financeiros através de uma Demonstração do Resultado do Exercício (DRE) hierárquica e interativa.
|
||||
|
||||
## Objetivo Principal
|
||||
|
||||
O sistema tem como objetivo principal fornecer uma interface intuitiva para análise de dados financeiros empresariais, permitindo:
|
||||
|
||||
- Visualização hierárquica de dados financeiros (Grupo → Subgrupo → Centro de Custo → Conta)
|
||||
- Análise temporal por períodos mensais
|
||||
- Drill-down analítico para detalhamento de transações
|
||||
- Exportação de dados para Excel
|
||||
- Cálculo de percentuais baseados em grupos de referência
|
||||
|
||||
## Características Principais
|
||||
|
||||
### 1. **Interface Hierárquica**
|
||||
- Estrutura em árvore expansível (Grupo → Subgrupo → Centro de Custo → Conta)
|
||||
- Visualização de valores e percentuais por mês
|
||||
- Ordenação por descrição ou valor total
|
||||
- Seleção de linhas para análise detalhada
|
||||
|
||||
### 2. **Análise Analítica**
|
||||
- Drill-down a partir de qualquer nível hierárquico
|
||||
- Filtros por período, centro de custo, grupo, subgrupo e conta
|
||||
- Visualização detalhada de transações individuais
|
||||
- Exportação para Excel com múltiplas abas
|
||||
|
||||
### 3. **Cálculos Automáticos**
|
||||
- Percentuais baseados no Grupo 03 como referência
|
||||
- Totais consolidados por nível hierárquico
|
||||
- Valores por mês com formatação monetária brasileira
|
||||
|
||||
## Estrutura do Projeto
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/
|
||||
│ ├── api/
|
||||
│ │ ├── analitico/route.ts # API para dados analíticos
|
||||
│ │ └── dre/route.ts # API para dados DRE
|
||||
│ ├── DRE/
|
||||
│ │ ├── analitico.tsx # Componente de análise analítica
|
||||
│ │ ├── page.tsx # Página principal
|
||||
│ │ └── teste.tsx # Componente principal DRE
|
||||
│ └── layout.tsx # Layout da aplicação
|
||||
├── components/
|
||||
│ └── ui/ # Componentes UI reutilizáveis
|
||||
├── db/
|
||||
│ ├── index.ts # Configuração do banco
|
||||
│ └── schema.ts # Schema do banco de dados
|
||||
└── lib/
|
||||
└── utils.ts # Utilitários
|
||||
```
|
||||
|
||||
## Tecnologias Utilizadas
|
||||
|
||||
- **Frontend**: Next.js 15, React 19, TypeScript
|
||||
- **Styling**: Tailwind CSS 4
|
||||
- **Database**: PostgreSQL com Drizzle ORM
|
||||
- **UI Components**: Radix UI, Lucide React
|
||||
- **Export**: XLSX para Excel
|
||||
|
||||
## Documentação Detalhada
|
||||
|
||||
- [Arquitetura do Sistema](./architecture.md)
|
||||
- [Banco de Dados](./database.md)
|
||||
- [APIs](./api.md)
|
||||
- [Componentes](./components.md)
|
||||
- [Guia de Desenvolvimento](./development.md)
|
||||
- [Deploy e Configuração](./deployment.md)
|
||||
- [Troubleshooting](./troubleshooting.md)
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Instalar dependências: `npm install`
|
||||
2. Configurar variáveis de ambiente (ver [Deploy](./deployment.md))
|
||||
3. Executar: `npm run dev`
|
||||
4. Acessar: `http://localhost:3000/DRE`
|
||||
|
||||
## Manutenção
|
||||
|
||||
Para manter o sistema sem perder suas características:
|
||||
|
||||
1. **Preserve a hierarquia**: Grupo → Subgrupo → Centro de Custo → Conta
|
||||
2. **Mantenha os cálculos**: Percentuais baseados no Grupo 03
|
||||
3. **Conserve a funcionalidade**: Drill-down e exportação Excel
|
||||
4. **Atualize dados**: Mantenha sincronização com fonte de dados
|
||||
5. **Teste filtros**: Valide todos os filtros analíticos
|
||||
|
||||
---
|
||||
|
||||
*Última atualização: $(date)*
|
||||
|
|
@ -0,0 +1,377 @@
|
|||
# APIs - DRE Gerencial
|
||||
|
||||
## Visão Geral
|
||||
|
||||
O sistema possui duas APIs principais construídas com Next.js App Router, utilizando Drizzle ORM para interação com PostgreSQL.
|
||||
|
||||
## Estrutura das APIs
|
||||
|
||||
### 1. **API DRE Gerencial** (`/api/dre/route.ts`)
|
||||
|
||||
#### Endpoint
|
||||
```
|
||||
GET /api/dre
|
||||
```
|
||||
|
||||
#### Descrição
|
||||
Retorna dados consolidados da view `view_dre_gerencial` para construção da interface hierárquica.
|
||||
|
||||
#### Implementação
|
||||
```typescript
|
||||
import db from '@/db';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const data = await db.execute(sql`SELECT * FROM view_dre_gerencial`);
|
||||
return NextResponse.json(data.rows);
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar dados da view:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erro ao carregar dados' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
```typescript
|
||||
interface DREItem {
|
||||
codfilial: string;
|
||||
data_competencia: string;
|
||||
data_caixa: string;
|
||||
grupo: string;
|
||||
subgrupo: string;
|
||||
centro_custo: string;
|
||||
codigo_conta: number;
|
||||
conta: string;
|
||||
valor: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### Casos de Uso
|
||||
- Carregamento inicial da interface DRE
|
||||
- Construção da hierarquia Grupo → Subgrupo → Centro de Custo → Conta
|
||||
- Cálculo de totais e percentuais
|
||||
|
||||
---
|
||||
|
||||
### 2. **API Analítica** (`/api/analitico/route.ts`)
|
||||
|
||||
#### Endpoint
|
||||
```
|
||||
GET /api/analitico?dataInicio=YYYY-MM&dataFim=YYYY-MM&[filtros]
|
||||
```
|
||||
|
||||
#### Parâmetros
|
||||
|
||||
| Parâmetro | Tipo | Obrigatório | Descrição |
|
||||
|-----------|------|-------------|-----------|
|
||||
| `dataInicio` | string | ✅ | Período inicial (YYYY-MM) |
|
||||
| `dataFim` | string | ✅ | Período final (YYYY-MM) |
|
||||
| `centroCusto` | string | ❌ | Filtro por centro de custo |
|
||||
| `codigoGrupo` | string | ❌ | Filtro por código do grupo |
|
||||
| `codigoSubgrupo` | string | ❌ | Filtro por código do subgrupo |
|
||||
| `codigoConta` | string | ❌ | Filtro por código da conta |
|
||||
|
||||
#### Implementação
|
||||
```typescript
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const dataInicio = searchParams.get('dataInicio');
|
||||
const dataFim = searchParams.get('dataFim');
|
||||
const centroCusto = searchParams.get('centroCusto');
|
||||
const codigoGrupo = searchParams.get('codigoGrupo');
|
||||
const codigoSubgrupo = searchParams.get('codigoSubgrupo');
|
||||
const codigoConta = searchParams.get('codigoConta');
|
||||
|
||||
if (!dataInicio || !dataFim) {
|
||||
return NextResponse.json(
|
||||
{ message: 'Parâmetros obrigatórios: dataInicio, dataFim' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Construção dinâmica da query baseada nos filtros
|
||||
let query;
|
||||
|
||||
if (centroCusto || codigoGrupo || codigoSubgrupo || codigoConta) {
|
||||
// Query com filtros específicos
|
||||
query = buildFilteredQuery(dataInicio, dataFim, filtros);
|
||||
} else {
|
||||
// Query simples por período
|
||||
query = buildSimpleQuery(dataInicio, dataFim);
|
||||
}
|
||||
|
||||
const data = await db.execute(query);
|
||||
return NextResponse.json(data.rows);
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar dados analíticos:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: 'Erro ao buscar dados analíticos',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
```typescript
|
||||
interface AnaliticoItem {
|
||||
codigo_grupo: string;
|
||||
codigo_subgrupo: string;
|
||||
codigo_fornecedor: string;
|
||||
nome_fornecedor: string;
|
||||
id: number;
|
||||
codfilial: string;
|
||||
recnum: number;
|
||||
data_competencia: string;
|
||||
data_vencimento: string;
|
||||
data_pagamento: string;
|
||||
data_caixa: string;
|
||||
codigo_conta: string;
|
||||
conta: string;
|
||||
codigo_centrocusto: string;
|
||||
valor: number;
|
||||
historico: string;
|
||||
historico2: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Estratégias de Query
|
||||
|
||||
### 1. **Query Simples (Sem Filtros Específicos)**
|
||||
```sql
|
||||
SELECT
|
||||
ffa.codigo_fornecedor,
|
||||
ffa.nome_fornecedor,
|
||||
ffa.id,
|
||||
ffa.codfilial,
|
||||
ffa.recnum,
|
||||
ffa.data_competencia,
|
||||
ffa.data_vencimento,
|
||||
ffa.data_pagamento,
|
||||
ffa.data_caixa,
|
||||
ffa.codigo_conta,
|
||||
ffa.conta,
|
||||
ffa.codigo_centrocusto,
|
||||
ffa.valor,
|
||||
ffa.historico,
|
||||
ffa.historico2,
|
||||
ffa.created_at,
|
||||
ffa.updated_at
|
||||
FROM fato_financeiro_analitico AS ffa
|
||||
WHERE to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $1 AND $2
|
||||
```
|
||||
|
||||
### 2. **Query com Filtros de Centro de Custo e Conta**
|
||||
```sql
|
||||
SELECT ffa.*
|
||||
FROM fato_financeiro_analitico AS ffa
|
||||
WHERE to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $1 AND $2
|
||||
AND ffa.codigo_centrocusto = $3
|
||||
AND ffa.codigo_conta = $4
|
||||
```
|
||||
|
||||
### 3. **Query com Filtros de Grupo/Subgrupo**
|
||||
```sql
|
||||
SELECT ffa.*
|
||||
FROM fato_financeiro_analitico AS ffa
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM public.view_dre_gerencial AS dre
|
||||
WHERE ffa.codigo_conta = dre.codigo_conta::text
|
||||
AND ffa.codigo_centrocusto = dre.centro_custo
|
||||
AND to_char(ffa.data_competencia, 'YYYY-MM') = to_char(dre.data_competencia, 'YYYY-MM')
|
||||
AND SUBSTRING(dre.grupo FROM '^\\s*(\\d+)\\s*\\.') = $1
|
||||
AND SUBSTRING(dre.subgrupo FROM '^\\s*(\\d+(?:\\.\\d+)+)\\s*-') = $2
|
||||
)
|
||||
AND to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $3 AND $4
|
||||
```
|
||||
|
||||
## Tratamento de Erros
|
||||
|
||||
### 1. **Validação de Parâmetros**
|
||||
```typescript
|
||||
if (!dataInicio || !dataFim) {
|
||||
return NextResponse.json(
|
||||
{ message: 'Parâmetros obrigatórios: dataInicio, dataFim' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **Tratamento de Erros de Banco**
|
||||
```typescript
|
||||
try {
|
||||
const data = await db.execute(query);
|
||||
return NextResponse.json(data.rows);
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar dados:', error);
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: 'Erro ao buscar dados',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **Códigos de Status HTTP**
|
||||
|
||||
| Status | Cenário | Response |
|
||||
|--------|---------|----------|
|
||||
| 200 | Sucesso | Dados solicitados |
|
||||
| 400 | Parâmetros inválidos | Mensagem de erro |
|
||||
| 500 | Erro interno | Detalhes do erro |
|
||||
|
||||
## Performance e Otimização
|
||||
|
||||
### 1. **Índices Recomendados**
|
||||
```sql
|
||||
-- Para filtros por data
|
||||
CREATE INDEX idx_fato_financeiro_data_competencia
|
||||
ON fato_financeiro_analitico (data_competencia);
|
||||
|
||||
-- Para filtros por centro de custo
|
||||
CREATE INDEX idx_fato_financeiro_centro_custo
|
||||
ON fato_financeiro_analitico (codigo_centrocusto);
|
||||
|
||||
-- Para filtros por conta
|
||||
CREATE INDEX idx_fato_financeiro_conta
|
||||
ON fato_financeiro_analitico (codigo_conta);
|
||||
```
|
||||
|
||||
### 2. **Estratégias de Cache**
|
||||
- **Client-side**: React Query para cache de dados
|
||||
- **Server-side**: Cache de views materializadas
|
||||
- **CDN**: Para assets estáticos
|
||||
|
||||
### 3. **Paginação (Futuro)**
|
||||
```typescript
|
||||
interface PaginatedResponse<T> {
|
||||
data: T[];
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Segurança
|
||||
|
||||
### 1. **Validação de Input**
|
||||
- Sanitização de parâmetros de query
|
||||
- Validação de tipos de dados
|
||||
- Escape de caracteres especiais
|
||||
|
||||
### 2. **SQL Injection Prevention**
|
||||
- Uso de prepared statements via Drizzle
|
||||
- Parâmetros tipados
|
||||
- Validação de entrada
|
||||
|
||||
### 3. **Rate Limiting (Futuro)**
|
||||
```typescript
|
||||
// Implementação de rate limiting
|
||||
const rateLimit = new Map();
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const ip = request.ip;
|
||||
const now = Date.now();
|
||||
const windowMs = 15 * 60 * 1000; // 15 minutos
|
||||
const maxRequests = 100;
|
||||
|
||||
// Lógica de rate limiting
|
||||
}
|
||||
```
|
||||
|
||||
## Monitoramento
|
||||
|
||||
### 1. **Logs Estruturados**
|
||||
```typescript
|
||||
console.log({
|
||||
timestamp: new Date().toISOString(),
|
||||
endpoint: '/api/analitico',
|
||||
method: 'GET',
|
||||
params: { dataInicio, dataFim, centroCusto },
|
||||
duration: Date.now() - startTime,
|
||||
status: 'success'
|
||||
});
|
||||
```
|
||||
|
||||
### 2. **Métricas de Performance**
|
||||
- Tempo de resposta por endpoint
|
||||
- Número de requisições por minuto
|
||||
- Taxa de erro por endpoint
|
||||
- Uso de memória e CPU
|
||||
|
||||
### 3. **Health Check (Futuro)**
|
||||
```typescript
|
||||
// GET /api/health
|
||||
export async function GET() {
|
||||
try {
|
||||
await db.execute(sql`SELECT 1`);
|
||||
return NextResponse.json({ status: 'healthy', timestamp: new Date() });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ status: 'unhealthy', error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testes
|
||||
|
||||
### 1. **Testes Unitários**
|
||||
```typescript
|
||||
// Exemplo de teste para API DRE
|
||||
describe('/api/dre', () => {
|
||||
it('should return DRE data', async () => {
|
||||
const response = await fetch('/api/dre');
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(Array.isArray(data)).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 2. **Testes de Integração**
|
||||
```typescript
|
||||
// Teste com filtros
|
||||
describe('/api/analitico', () => {
|
||||
it('should filter by date range', async () => {
|
||||
const params = new URLSearchParams({
|
||||
dataInicio: '2024-01',
|
||||
dataFim: '2024-12'
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/analitico?${params}`);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(data.every(item =>
|
||||
item.data_competencia.startsWith('2024')
|
||||
)).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Próximos Passos
|
||||
|
||||
1. **Implementar Autenticação** JWT
|
||||
2. **Adicionar Rate Limiting** por IP
|
||||
3. **Implementar Cache Redis** para queries frequentes
|
||||
4. **Adicionar Paginação** para grandes volumes
|
||||
5. **Implementar Webhooks** para notificações
|
||||
6. **Adicionar Documentação OpenAPI** (Swagger)
|
||||
7. **Implementar Versionamento** de API
|
||||
8. **Adicionar Monitoramento** com Prometheus/Grafana
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
# Arquitetura do Sistema DRE Gerencial
|
||||
|
||||
## Visão Geral da Arquitetura
|
||||
|
||||
O sistema DRE Gerencial segue uma arquitetura moderna baseada em Next.js com App Router, utilizando uma abordagem de componentes React funcionais e TypeScript para type safety.
|
||||
|
||||
## Padrões Arquiteturais
|
||||
|
||||
### 1. **Arquitetura em Camadas**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Frontend Layer │
|
||||
│ (React Components + Tailwind CSS) │
|
||||
├─────────────────────────────────────┤
|
||||
│ API Layer │
|
||||
│ (Next.js API Routes) │
|
||||
├─────────────────────────────────────┤
|
||||
│ Database Layer │
|
||||
│ (PostgreSQL + Drizzle ORM) │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2. **Estrutura de Componentes**
|
||||
|
||||
#### Componente Principal (`teste.tsx`)
|
||||
- **Responsabilidade**: Orquestração da interface DRE hierárquica
|
||||
- **Estado**: Gerencia expansão/colapso, ordenação, filtros analíticos
|
||||
- **Padrão**: Container Component com lógica de negócio
|
||||
|
||||
#### Componente Analítico (`analitico.tsx`)
|
||||
- **Responsabilidade**: Visualização detalhada de transações
|
||||
- **Estado**: Dados analíticos, ordenação, loading
|
||||
- **Padrão**: Presentational Component com funcionalidades específicas
|
||||
|
||||
### 3. **Gerenciamento de Estado**
|
||||
|
||||
#### Estados Locais por Componente
|
||||
```typescript
|
||||
// Estados de expansão hierárquica
|
||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
||||
const [expandedSubgrupos, setExpandedSubgrupos] = useState<Set<string>>(new Set());
|
||||
const [expandedCentros, setExpandedCentros] = useState<Set<string>>(new Set());
|
||||
|
||||
// Estados de ordenação
|
||||
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
||||
field: 'descricao',
|
||||
direction: 'asc',
|
||||
});
|
||||
|
||||
// Estados de filtros analíticos
|
||||
const [analiticoFiltros, setAnaliticoFiltros] = useState({
|
||||
dataInicio: '',
|
||||
dataFim: '',
|
||||
centroCusto: '',
|
||||
codigoGrupo: '',
|
||||
codigoSubgrupo: '',
|
||||
codigoConta: '',
|
||||
});
|
||||
```
|
||||
|
||||
### 4. **Padrões de Dados**
|
||||
|
||||
#### Hierarquia de Dados
|
||||
```typescript
|
||||
interface HierarchicalRow {
|
||||
type: 'grupo' | 'subgrupo' | 'centro_custo' | 'conta';
|
||||
level: number;
|
||||
grupo?: string;
|
||||
subgrupo?: string;
|
||||
centro_custo?: string;
|
||||
conta?: string;
|
||||
codigo_conta?: number;
|
||||
total?: number;
|
||||
isExpanded?: boolean;
|
||||
valoresPorMes?: Record<string, number>;
|
||||
percentuaisPorMes?: Record<string, number>;
|
||||
}
|
||||
```
|
||||
|
||||
#### Transformação de Dados
|
||||
- **Agregação**: Dados brutos → Hierarquia estruturada
|
||||
- **Cálculos**: Valores por mês e percentuais automáticos
|
||||
- **Ordenação**: Por descrição ou valor total
|
||||
|
||||
## Fluxo de Dados
|
||||
|
||||
### 1. **Carregamento Inicial**
|
||||
```
|
||||
API /api/dre → Dados brutos → buildHierarchicalData() → Interface hierárquica
|
||||
```
|
||||
|
||||
### 2. **Interação do Usuário**
|
||||
```
|
||||
Clique em linha → handleRowClick() → setAnaliticoFiltros() → AnaliticoComponent
|
||||
```
|
||||
|
||||
### 3. **Análise Analítica**
|
||||
```
|
||||
Filtros → API /api/analitico → Dados detalhados → Tabela analítica
|
||||
```
|
||||
|
||||
## Padrões de Design
|
||||
|
||||
### 1. **Component Composition**
|
||||
- Componentes pequenos e focados
|
||||
- Props tipadas com TypeScript
|
||||
- Separação de responsabilidades
|
||||
|
||||
### 2. **Custom Hooks (Potencial)**
|
||||
```typescript
|
||||
// Exemplo de hook customizado para dados DRE
|
||||
const useDREData = () => {
|
||||
const [data, setData] = useState<DREItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
// Lógica de fetch
|
||||
}, []);
|
||||
|
||||
return { data, loading, fetchData };
|
||||
};
|
||||
```
|
||||
|
||||
### 3. **Error Boundaries**
|
||||
- Tratamento de erros em componentes
|
||||
- Estados de loading e error
|
||||
- Fallbacks visuais
|
||||
|
||||
## Performance
|
||||
|
||||
### 1. **Otimizações Implementadas**
|
||||
- `useCallback` para funções de fetch
|
||||
- `useMemo` para cálculos pesados (potencial)
|
||||
- Lazy loading de componentes (potencial)
|
||||
|
||||
### 2. **Estratégias de Renderização**
|
||||
- Renderização condicional baseada em estado
|
||||
- Virtualização para listas grandes (potencial)
|
||||
- Debounce para filtros (potencial)
|
||||
|
||||
## Escalabilidade
|
||||
|
||||
### 1. **Estrutura Modular**
|
||||
- Componentes reutilizáveis em `/components/ui`
|
||||
- APIs separadas por funcionalidade
|
||||
- Schema de banco bem definido
|
||||
|
||||
### 2. **Extensibilidade**
|
||||
- Fácil adição de novos níveis hierárquicos
|
||||
- Suporte a novos tipos de filtros
|
||||
- Integração com outras fontes de dados
|
||||
|
||||
## Segurança
|
||||
|
||||
### 1. **Validação de Dados**
|
||||
- TypeScript para type safety
|
||||
- Validação de parâmetros nas APIs
|
||||
- Sanitização de queries SQL
|
||||
|
||||
### 2. **Controle de Acesso**
|
||||
- Autenticação (a implementar)
|
||||
- Autorização por níveis (a implementar)
|
||||
- Logs de auditoria (a implementar)
|
||||
|
||||
## Monitoramento
|
||||
|
||||
### 1. **Logs**
|
||||
- Console logs para debugging
|
||||
- Error tracking (a implementar)
|
||||
- Performance monitoring (a implementar)
|
||||
|
||||
### 2. **Métricas**
|
||||
- Tempo de carregamento de dados
|
||||
- Uso de filtros
|
||||
- Exportações realizadas
|
||||
|
||||
## Próximos Passos Arquiteturais
|
||||
|
||||
1. **Implementar Context API** para estado global
|
||||
2. **Adicionar React Query** para cache de dados
|
||||
3. **Implementar Error Boundaries** robustos
|
||||
4. **Adicionar testes unitários** e de integração
|
||||
5. **Implementar autenticação** e autorização
|
||||
6. **Adicionar monitoramento** e analytics
|
||||
|
|
@ -0,0 +1,539 @@
|
|||
# Componentes - DRE Gerencial
|
||||
|
||||
## Visão Geral
|
||||
|
||||
O sistema DRE Gerencial é construído com componentes React funcionais em TypeScript, seguindo padrões modernos de desenvolvimento frontend.
|
||||
|
||||
## Estrutura de Componentes
|
||||
|
||||
### 1. **Componente Principal** (`src/app/DRE/teste.tsx`)
|
||||
|
||||
#### Responsabilidades
|
||||
- Orquestração da interface DRE hierárquica
|
||||
- Gerenciamento de estado de expansão/colapso
|
||||
- Controle de ordenação e filtros
|
||||
- Integração com componente analítico
|
||||
|
||||
#### Estados Principais
|
||||
```typescript
|
||||
const [data, setData] = useState<DREItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
||||
const [expandedSubgrupos, setExpandedSubgrupos] = useState<Set<string>>(new Set());
|
||||
const [expandedCentros, setExpandedCentros] = useState<Set<string>>(new Set());
|
||||
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
||||
field: 'descricao',
|
||||
direction: 'asc',
|
||||
});
|
||||
const [analiticoFiltros, setAnaliticoFiltros] = useState({
|
||||
dataInicio: '',
|
||||
dataFim: '',
|
||||
centroCusto: '',
|
||||
codigoGrupo: '',
|
||||
codigoSubgrupo: '',
|
||||
codigoConta: '',
|
||||
});
|
||||
```
|
||||
|
||||
#### Funções Principais
|
||||
|
||||
##### `fetchData()`
|
||||
```typescript
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const response = await fetch('/api/dre');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erro ao carregar dados: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
setData(result);
|
||||
|
||||
// Extrair meses únicos dos dados
|
||||
const meses = [...new Set(
|
||||
result.map((item: DREItem) => {
|
||||
const dataCompetencia = new Date(item.data_competencia);
|
||||
return `${dataCompetencia.getFullYear()}-${String(
|
||||
dataCompetencia.getMonth() + 1
|
||||
).padStart(2, '0')}`;
|
||||
})
|
||||
)].sort() as string[];
|
||||
|
||||
setMesesDisponiveis(meses);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Erro desconhecido');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
##### `buildHierarchicalData()`
|
||||
```typescript
|
||||
const buildHierarchicalData = (): HierarchicalRow[] => {
|
||||
const rows: HierarchicalRow[] = [];
|
||||
|
||||
// Agrupar por grupo, tratando grupo 05 como subgrupo do grupo 04
|
||||
const grupos = data.reduce((acc, item) => {
|
||||
if (item.grupo.includes('05')) {
|
||||
// Lógica especial para grupo 05
|
||||
const grupo04Key = Object.keys(acc).find((key) => key.includes('04'));
|
||||
if (grupo04Key) {
|
||||
acc[grupo04Key].push(item);
|
||||
} else {
|
||||
const grupo04Nome = '04 - GRUPO 04';
|
||||
if (!acc[grupo04Nome]) {
|
||||
acc[grupo04Nome] = [];
|
||||
}
|
||||
acc[grupo04Nome].push(item);
|
||||
}
|
||||
} else {
|
||||
if (!acc[item.grupo]) {
|
||||
acc[item.grupo] = [];
|
||||
}
|
||||
acc[item.grupo].push(item);
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, DREItem[]>);
|
||||
|
||||
// Construir hierarquia completa
|
||||
// ... lógica de construção hierárquica
|
||||
|
||||
return rows;
|
||||
};
|
||||
```
|
||||
|
||||
##### `handleRowClick()`
|
||||
```typescript
|
||||
const handleRowClick = (row: HierarchicalRow, mesSelecionado?: string) => {
|
||||
if (!data.length) return;
|
||||
|
||||
// Calcular período baseado nos dados
|
||||
const datas = data.map((item) => item.data_competencia);
|
||||
const dataInicio = Math.min(...datas.map((d) => new Date(d).getTime()));
|
||||
const dataFim = Math.max(...datas.map((d) => new Date(d).getTime()));
|
||||
|
||||
const dataInicioStr = new Date(dataInicio).toISOString().substring(0, 7);
|
||||
const dataFimStr = new Date(dataFim).toISOString().substring(0, 7);
|
||||
|
||||
const { codigoGrupo, codigoSubgrupo } = extractCodes(
|
||||
row.grupo || '',
|
||||
row.subgrupo
|
||||
);
|
||||
|
||||
// Criar identificador único para a linha
|
||||
const linhaId = `${row.type}-${row.grupo || ''}-${row.subgrupo || ''}-${
|
||||
row.centro_custo || ''
|
||||
}-${row.codigo_conta || ''}`;
|
||||
setLinhaSelecionada(linhaId);
|
||||
|
||||
// Configurar filtros para análise analítica
|
||||
const dataInicioFiltro = mesSelecionado || dataInicioStr;
|
||||
const dataFimFiltro = mesSelecionado || dataFimStr;
|
||||
|
||||
setAnaliticoFiltros({
|
||||
dataInicio: dataInicioFiltro,
|
||||
dataFim: dataFimFiltro,
|
||||
centroCusto: row.centro_custo || '',
|
||||
codigoGrupo,
|
||||
codigoSubgrupo,
|
||||
codigoConta: row.codigo_conta?.toString() || '',
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
#### Renderização
|
||||
```typescript
|
||||
return (
|
||||
<div className="w-full flex flex-col items-center gap-2">
|
||||
<div className="mb-1">
|
||||
<h1 className="text-lg font-bold">DRE Gerencial</h1>
|
||||
</div>
|
||||
|
||||
{/* Tabela hierárquica */}
|
||||
<div className="w-[95%] max-h-[400px] overflow-y-auto border rounded-md relative">
|
||||
{/* Header fixo */}
|
||||
<div className="sticky top-0 z-30 border-b shadow-sm">
|
||||
{/* ... header content */}
|
||||
</div>
|
||||
|
||||
{/* Dados hierárquicos */}
|
||||
<div className="flex flex-col">
|
||||
{hierarchicalData.map((row, index) => (
|
||||
<div key={index} className={`flex ${getRowStyle(row)}`}>
|
||||
{/* ... row content */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Componente Analítico */}
|
||||
{!loading && data.length > 0 && (
|
||||
<AnaliticoComponent filtros={analiticoFiltros} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. **Componente Analítico** (`src/app/DRE/analitico.tsx`)
|
||||
|
||||
#### Responsabilidades
|
||||
- Visualização detalhada de transações
|
||||
- Ordenação de dados analíticos
|
||||
- Exportação para Excel
|
||||
- Aplicação de filtros dinâmicos
|
||||
|
||||
#### Props
|
||||
```typescript
|
||||
interface AnaliticoProps {
|
||||
filtros: {
|
||||
dataInicio: string;
|
||||
dataFim: string;
|
||||
centroCusto?: string;
|
||||
codigoGrupo?: string;
|
||||
codigoSubgrupo?: string;
|
||||
codigoConta?: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### Estados
|
||||
```typescript
|
||||
const [data, setData] = useState<AnaliticoItem[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
||||
field: 'data_competencia',
|
||||
direction: 'desc',
|
||||
});
|
||||
```
|
||||
|
||||
#### Funções Principais
|
||||
|
||||
##### `fetchData()`
|
||||
```typescript
|
||||
const fetchData = useCallback(async () => {
|
||||
if (!filtros.dataInicio || !filtros.dataFim) {
|
||||
setData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
dataInicio: filtros.dataInicio,
|
||||
dataFim: filtros.dataFim,
|
||||
...(filtros.centroCusto && { centroCusto: filtros.centroCusto }),
|
||||
...(filtros.codigoGrupo && { codigoGrupo: filtros.codigoGrupo }),
|
||||
...(filtros.codigoSubgrupo && { codigoSubgrupo: filtros.codigoSubgrupo }),
|
||||
...(filtros.codigoConta && { codigoConta: filtros.codigoConta }),
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/analitico?${params}`);
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
setData(result as AnaliticoItem[]);
|
||||
} else {
|
||||
console.error('Erro ao buscar dados:', await response.text());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar dados:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [filtros]);
|
||||
```
|
||||
|
||||
##### `exportToExcel()`
|
||||
```typescript
|
||||
const exportToExcel = () => {
|
||||
if (data.length === 0) return;
|
||||
|
||||
// Preparar dados para exportação
|
||||
const exportData = data.map((item) => ({
|
||||
'Data Competência': new Date(item.data_competencia).toLocaleDateString('pt-BR'),
|
||||
'Data Vencimento': new Date(item.data_vencimento).toLocaleDateString('pt-BR'),
|
||||
'Data Caixa': new Date(item.data_caixa).toLocaleDateString('pt-BR'),
|
||||
'Código Fornecedor': item.codigo_fornecedor,
|
||||
Fornecedor: item.nome_fornecedor,
|
||||
'Código Centro Custo': item.codigo_centrocusto,
|
||||
'Centro Custo': item.codigo_centrocusto,
|
||||
'Código Conta': item.codigo_conta,
|
||||
Conta: item.conta,
|
||||
Valor: typeof item.valor === 'string' ? parseFloat(item.valor) : item.valor,
|
||||
Histórico: item.historico,
|
||||
'Histórico 2': item.historico2,
|
||||
Recnum: item.recnum,
|
||||
}));
|
||||
|
||||
// Criar workbook
|
||||
const wb = XLSX.utils.book_new();
|
||||
const ws = XLSX.utils.json_to_sheet(exportData);
|
||||
|
||||
// Adicionar resumo na segunda aba
|
||||
const resumoData = [
|
||||
{ Métrica: 'Total de Registros', Valor: data.length },
|
||||
{ Métrica: 'Valor Total', Valor: totalValor },
|
||||
];
|
||||
const wsResumo = XLSX.utils.json_to_sheet(resumoData);
|
||||
|
||||
// Adicionar abas ao workbook
|
||||
XLSX.utils.book_append_sheet(wb, ws, 'Dados Analíticos');
|
||||
XLSX.utils.book_append_sheet(wb, wsResumo, 'Resumo');
|
||||
|
||||
// Gerar nome do arquivo com timestamp
|
||||
const now = new Date();
|
||||
const timestamp = now.toISOString().slice(0, 19).replace(/:/g, '-');
|
||||
const fileName = `analitico_${timestamp}.xlsx`;
|
||||
|
||||
// Fazer download
|
||||
XLSX.writeFile(wb, fileName);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. **Componentes UI** (`src/components/ui/`)
|
||||
|
||||
#### Button Component
|
||||
```typescript
|
||||
// src/components/ui/button.tsx
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Button.displayName = 'Button';
|
||||
|
||||
export { Button, buttonVariants };
|
||||
```
|
||||
|
||||
## Padrões de Design
|
||||
|
||||
### 1. **Composition Pattern**
|
||||
- Componentes pequenos e focados
|
||||
- Props tipadas com TypeScript
|
||||
- Reutilização através de composition
|
||||
|
||||
### 2. **State Management**
|
||||
- Estados locais com `useState`
|
||||
- Callbacks com `useCallback` para performance
|
||||
- Effects com `useEffect` para side effects
|
||||
|
||||
### 3. **Styling**
|
||||
- Tailwind CSS para styling
|
||||
- Class variance authority para variantes
|
||||
- Responsive design mobile-first
|
||||
|
||||
### 4. **Type Safety**
|
||||
- Interfaces TypeScript para props
|
||||
- Tipos específicos para dados
|
||||
- Validação de tipos em runtime
|
||||
|
||||
## Utilitários
|
||||
|
||||
### 1. **Formatação**
|
||||
```typescript
|
||||
const formatCurrency = (value: number) => {
|
||||
return new Intl.NumberFormat('pt-BR', {
|
||||
style: 'currency',
|
||||
currency: 'BRL',
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
const formatCurrencyWithColor = (value: number) => {
|
||||
const formatted = formatCurrency(value);
|
||||
const isNegative = value < 0;
|
||||
return { formatted, isNegative };
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('pt-BR');
|
||||
};
|
||||
```
|
||||
|
||||
### 2. **Extração de Códigos**
|
||||
```typescript
|
||||
const extractCodes = (grupo: string, subgrupo?: string) => {
|
||||
const grupoMatch = grupo.match(/^(\d+)/);
|
||||
const codigoGrupo = grupoMatch ? grupoMatch[1] : '';
|
||||
|
||||
let codigoSubgrupo = '';
|
||||
if (subgrupo) {
|
||||
const subgrupoMatch = subgrupo.match(/^(\d+(?:\.\d+)+)/);
|
||||
if (subgrupoMatch) {
|
||||
codigoSubgrupo = subgrupoMatch[1];
|
||||
} else {
|
||||
codigoSubgrupo = subgrupo;
|
||||
}
|
||||
}
|
||||
|
||||
return { codigoGrupo, codigoSubgrupo };
|
||||
};
|
||||
```
|
||||
|
||||
### 3. **Cálculos**
|
||||
```typescript
|
||||
const calcularValoresPorMes = (items: DREItem[]): Record<string, number> => {
|
||||
const valoresPorMes: Record<string, number> = {};
|
||||
|
||||
items.forEach((item) => {
|
||||
const dataCompetencia = new Date(item.data_competencia);
|
||||
const anoMes = `${dataCompetencia.getFullYear()}-${String(
|
||||
dataCompetencia.getMonth() + 1
|
||||
).padStart(2, '0')}`;
|
||||
|
||||
if (!valoresPorMes[anoMes]) {
|
||||
valoresPorMes[anoMes] = 0;
|
||||
}
|
||||
valoresPorMes[anoMes] += parseFloat(item.valor);
|
||||
});
|
||||
|
||||
return valoresPorMes;
|
||||
};
|
||||
|
||||
const calcularPercentuaisPorMes = (
|
||||
valoresPorMes: Record<string, number>,
|
||||
grupo: string
|
||||
): Record<string, number> => {
|
||||
const percentuais: Record<string, number> = {};
|
||||
|
||||
// Se for o grupo 03, retorna 100% para todos os meses
|
||||
if (grupo.includes('03')) {
|
||||
Object.keys(valoresPorMes).forEach((mes) => {
|
||||
percentuais[mes] = 100;
|
||||
});
|
||||
return percentuais;
|
||||
}
|
||||
|
||||
// Para outros grupos, calcular percentual baseado no grupo 03
|
||||
Object.keys(valoresPorMes).forEach((mes) => {
|
||||
const valorAtual = valoresPorMes[mes];
|
||||
|
||||
// Encontrar o valor do grupo 03 para o mesmo mês
|
||||
const grupo03Items = data.filter((item) => {
|
||||
const dataCompetencia = new Date(item.data_competencia);
|
||||
const anoMes = `${dataCompetencia.getFullYear()}-${String(
|
||||
dataCompetencia.getMonth() + 1
|
||||
).padStart(2, '0')}`;
|
||||
return anoMes === mes && item.grupo.includes('03');
|
||||
});
|
||||
|
||||
const valorGrupo03 = grupo03Items.reduce(
|
||||
(sum, item) => sum + parseFloat(item.valor),
|
||||
0
|
||||
);
|
||||
|
||||
if (valorGrupo03 !== 0) {
|
||||
percentuais[mes] = (valorAtual / valorGrupo03) * 100;
|
||||
} else {
|
||||
percentuais[mes] = 0;
|
||||
}
|
||||
});
|
||||
|
||||
return percentuais;
|
||||
};
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### 1. **Otimizações Implementadas**
|
||||
- `useCallback` para funções de fetch
|
||||
- `useMemo` para cálculos pesados (potencial)
|
||||
- Renderização condicional
|
||||
|
||||
### 2. **Estratégias de Renderização**
|
||||
- Lazy loading de componentes
|
||||
- Virtualização para listas grandes (potencial)
|
||||
- Debounce para filtros (potencial)
|
||||
|
||||
## Testes
|
||||
|
||||
### 1. **Testes Unitários**
|
||||
```typescript
|
||||
// Exemplo de teste para componente
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import Teste from './teste';
|
||||
|
||||
describe('Teste Component', () => {
|
||||
it('renders DRE title', () => {
|
||||
render(<Teste />);
|
||||
expect(screen.getByText('DRE Gerencial')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 2. **Testes de Integração**
|
||||
```typescript
|
||||
// Teste de interação com API
|
||||
describe('DRE Integration', () => {
|
||||
it('loads data from API', async () => {
|
||||
render(<Teste />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Carregando dados...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Verificar se dados foram carregados
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Próximos Passos
|
||||
|
||||
1. **Implementar Context API** para estado global
|
||||
2. **Adicionar React Query** para cache de dados
|
||||
3. **Implementar Error Boundaries** robustos
|
||||
4. **Adicionar testes unitários** e de integração
|
||||
5. **Implementar lazy loading** de componentes
|
||||
6. **Adicionar acessibilidade** (ARIA labels)
|
||||
7. **Implementar temas** dark/light
|
||||
8. **Adicionar animações** e transições
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
# Banco de Dados - DRE Gerencial
|
||||
|
||||
## Visão Geral
|
||||
|
||||
O sistema utiliza PostgreSQL como banco de dados principal, com Drizzle ORM para type-safe database operations. A estrutura é baseada em views materializadas para performance otimizada.
|
||||
|
||||
## Configuração do Banco
|
||||
|
||||
### Variáveis de Ambiente
|
||||
```env
|
||||
POSTGRES_DB=dre_gerencial
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=sua_senha
|
||||
```
|
||||
|
||||
### Conexão (src/db/index.ts)
|
||||
```typescript
|
||||
import 'dotenv/config';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { Pool } from 'pg';
|
||||
import * as schema from './schema';
|
||||
|
||||
const pool = new Pool({
|
||||
database: process.env.POSTGRES_DB,
|
||||
host: process.env.POSTGRES_HOST,
|
||||
port: Number(process.env.POSTGRES_PORT),
|
||||
user: process.env.POSTGRES_USER,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
});
|
||||
|
||||
const db = drizzle({
|
||||
client: pool,
|
||||
schema,
|
||||
});
|
||||
```
|
||||
|
||||
## Schema do Banco
|
||||
|
||||
### View Principal (src/db/schema.ts)
|
||||
```typescript
|
||||
export const view = pgView('view_dre_gerencial', {
|
||||
codfilial: text(),
|
||||
data_competencia: date(),
|
||||
data_caixa: date(),
|
||||
grupo: text(),
|
||||
subgrupo: text(),
|
||||
centro_custo: text(),
|
||||
codigo_conta: integer(),
|
||||
conta: text(),
|
||||
valor: numeric(),
|
||||
});
|
||||
```
|
||||
|
||||
### Estrutura da View
|
||||
A view `view_dre_gerencial` consolida dados de múltiplas tabelas para fornecer uma estrutura hierárquica otimizada:
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `codfilial` | text | Código da filial |
|
||||
| `data_competencia` | date | Data de competência do lançamento |
|
||||
| `data_caixa` | date | Data de caixa |
|
||||
| `grupo` | text | Descrição do grupo (ex: "01 - RECEITAS") |
|
||||
| `subgrupo` | text | Descrição do subgrupo |
|
||||
| `centro_custo` | text | Centro de custo |
|
||||
| `codigo_conta` | integer | Código numérico da conta |
|
||||
| `conta` | text | Descrição da conta |
|
||||
| `valor` | numeric | Valor do lançamento |
|
||||
|
||||
## Tabelas Base (Inferidas)
|
||||
|
||||
### Tabela Principal: `fato_financeiro_analitico`
|
||||
Baseada na API analítica, esta tabela contém os dados detalhados:
|
||||
|
||||
| Campo | Tipo | Descrição |
|
||||
|-------|------|-----------|
|
||||
| `id` | integer | ID único |
|
||||
| `codfilial` | text | Código da filial |
|
||||
| `recnum` | integer | Número do registro |
|
||||
| `data_competencia` | date | Data de competência |
|
||||
| `data_vencimento` | date | Data de vencimento |
|
||||
| `data_pagamento` | date | Data de pagamento |
|
||||
| `data_caixa` | date | Data de caixa |
|
||||
| `codigo_conta` | text | Código da conta |
|
||||
| `conta` | text | Descrição da conta |
|
||||
| `codigo_centrocusto` | text | Código do centro de custo |
|
||||
| `codigo_fornecedor` | text | Código do fornecedor |
|
||||
| `nome_fornecedor` | text | Nome do fornecedor |
|
||||
| `valor` | numeric | Valor do lançamento |
|
||||
| `historico` | text | Histórico principal |
|
||||
| `historico2` | text | Histórico secundário |
|
||||
| `created_at` | timestamp | Data de criação |
|
||||
| `updated_at` | timestamp | Data de atualização |
|
||||
|
||||
## Queries Principais
|
||||
|
||||
### 1. **Dados DRE Gerencial**
|
||||
```sql
|
||||
SELECT * FROM view_dre_gerencial
|
||||
```
|
||||
- **Uso**: Carregamento inicial da interface hierárquica
|
||||
- **Performance**: Otimizada via view materializada
|
||||
|
||||
### 2. **Dados Analíticos com Filtros**
|
||||
```sql
|
||||
SELECT
|
||||
ffa.codigo_fornecedor,
|
||||
ffa.nome_fornecedor,
|
||||
ffa.id,
|
||||
ffa.codfilial,
|
||||
ffa.recnum,
|
||||
ffa.data_competencia,
|
||||
ffa.data_vencimento,
|
||||
ffa.data_pagamento,
|
||||
ffa.data_caixa,
|
||||
ffa.codigo_conta,
|
||||
ffa.conta,
|
||||
ffa.codigo_centrocusto,
|
||||
ffa.valor,
|
||||
ffa.historico,
|
||||
ffa.historico2,
|
||||
ffa.created_at,
|
||||
ffa.updated_at
|
||||
FROM fato_financeiro_analitico AS ffa
|
||||
WHERE to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $1 AND $2
|
||||
AND ffa.codigo_centrocusto = $3 -- Opcional
|
||||
AND ffa.codigo_conta = $4 -- Opcional
|
||||
```
|
||||
|
||||
### 3. **Query com Filtros de Grupo/Subgrupo**
|
||||
```sql
|
||||
SELECT ffa.*
|
||||
FROM fato_financeiro_analitico AS ffa
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM public.view_dre_gerencial AS dre
|
||||
WHERE ffa.codigo_conta = dre.codigo_conta::text
|
||||
AND ffa.codigo_centrocusto = dre.centro_custo
|
||||
AND to_char(ffa.data_competencia, 'YYYY-MM') = to_char(dre.data_competencia, 'YYYY-MM')
|
||||
AND SUBSTRING(dre.grupo FROM '^\\s*(\\d+)\\s*\\.') = $1
|
||||
AND SUBSTRING(dre.subgrupo FROM '^\\s*(\\d+(?:\\.\\d+)+)\\s*-') = $2
|
||||
)
|
||||
AND to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $3 AND $4
|
||||
```
|
||||
|
||||
## Índices Recomendados
|
||||
|
||||
### Índices para Performance
|
||||
```sql
|
||||
-- Índice para filtros por data
|
||||
CREATE INDEX idx_fato_financeiro_data_competencia
|
||||
ON fato_financeiro_analitico (data_competencia);
|
||||
|
||||
-- Índice para filtros por centro de custo
|
||||
CREATE INDEX idx_fato_financeiro_centro_custo
|
||||
ON fato_financeiro_analitico (codigo_centrocusto);
|
||||
|
||||
-- Índice para filtros por conta
|
||||
CREATE INDEX idx_fato_financeiro_conta
|
||||
ON fato_financeiro_analitico (codigo_conta);
|
||||
|
||||
-- Índice composto para queries analíticas
|
||||
CREATE INDEX idx_fato_financeiro_analitico_composto
|
||||
ON fato_financeiro_analitico (data_competencia, codigo_centrocusto, codigo_conta);
|
||||
```
|
||||
|
||||
## Migrações e Versionamento
|
||||
|
||||
### Drizzle Kit Configuration
|
||||
```typescript
|
||||
// drizzle.config.ts
|
||||
export default defineConfig({
|
||||
out: './drizzle',
|
||||
schema: './src/db/schema.ts',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
database: process.env.POSTGRES_DB || 'dre_gerencial',
|
||||
host: process.env.POSTGRES_HOST || 'localhost',
|
||||
port: Number(process.env.POSTGRES_PORT) || 5432,
|
||||
user: process.env.POSTGRES_USER || 'postgres',
|
||||
password: process.env.POSTGRES_PASSWORD || '',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Comandos de Migração
|
||||
```bash
|
||||
# Gerar migração
|
||||
npx drizzle-kit generate
|
||||
|
||||
# Aplicar migração
|
||||
npx drizzle-kit migrate
|
||||
|
||||
# Visualizar schema
|
||||
npx drizzle-kit studio
|
||||
```
|
||||
|
||||
## Backup e Manutenção
|
||||
|
||||
### Backup Automático
|
||||
```bash
|
||||
# Backup diário
|
||||
pg_dump -h localhost -U postgres -d dre_gerencial > backup_$(date +%Y%m%d).sql
|
||||
|
||||
# Restore
|
||||
psql -h localhost -U postgres -d dre_gerencial < backup_20240101.sql
|
||||
```
|
||||
|
||||
### Manutenção da View
|
||||
```sql
|
||||
-- Refresh da view materializada (se aplicável)
|
||||
REFRESH MATERIALIZED VIEW view_dre_gerencial;
|
||||
|
||||
-- Análise de performance
|
||||
ANALYZE fato_financeiro_analitico;
|
||||
ANALYZE view_dre_gerencial;
|
||||
```
|
||||
|
||||
## Monitoramento
|
||||
|
||||
### Queries de Monitoramento
|
||||
```sql
|
||||
-- Tamanho das tabelas
|
||||
SELECT
|
||||
schemaname,
|
||||
tablename,
|
||||
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size
|
||||
FROM pg_tables
|
||||
WHERE schemaname = 'public'
|
||||
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
|
||||
|
||||
-- Queries lentas
|
||||
SELECT query, mean_time, calls
|
||||
FROM pg_stat_statements
|
||||
ORDER BY mean_time DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problemas Comuns
|
||||
|
||||
1. **Conexão Recusada**
|
||||
- Verificar se PostgreSQL está rodando
|
||||
- Validar credenciais de conexão
|
||||
- Verificar firewall/portas
|
||||
|
||||
2. **Performance Lenta**
|
||||
- Verificar índices existentes
|
||||
- Analisar query execution plan
|
||||
- Considerar particionamento por data
|
||||
|
||||
3. **Dados Inconsistentes**
|
||||
- Verificar refresh da view
|
||||
- Validar integridade referencial
|
||||
- Executar VACUUM ANALYZE
|
||||
|
||||
## Próximos Passos
|
||||
|
||||
1. **Implementar Cache Redis** para queries frequentes
|
||||
2. **Adicionar Particionamento** por data para tabelas grandes
|
||||
3. **Implementar Replicação** para alta disponibilidade
|
||||
4. **Adicionar Monitoramento** de performance em tempo real
|
||||
5. **Implementar Backup Automático** com retenção configurável
|
||||
|
|
@ -0,0 +1,580 @@
|
|||
# Deploy e Configuração - DRE Gerencial
|
||||
|
||||
## Visão Geral
|
||||
|
||||
Este guia cobre o processo completo de deploy e configuração do sistema DRE Gerencial em diferentes ambientes.
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
### 1. **Ambiente de Desenvolvimento**
|
||||
- Node.js 18+
|
||||
- PostgreSQL 13+
|
||||
- npm ou yarn
|
||||
- Git
|
||||
|
||||
### 2. **Ambiente de Produção**
|
||||
- Servidor Linux (Ubuntu 20.04+ recomendado)
|
||||
- PostgreSQL 13+
|
||||
- Nginx (opcional, para proxy reverso)
|
||||
- PM2 ou similar (para gerenciamento de processos)
|
||||
|
||||
## Configuração do Ambiente
|
||||
|
||||
### 1. **Variáveis de Ambiente**
|
||||
|
||||
#### Desenvolvimento (`.env.local`)
|
||||
```env
|
||||
# Database
|
||||
POSTGRES_DB=dre_gerencial
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=dev_password
|
||||
|
||||
# Next.js
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
NODE_ENV=development
|
||||
```
|
||||
|
||||
#### Produção (`.env.production`)
|
||||
```env
|
||||
# Database
|
||||
POSTGRES_DB=dre_gerencial_prod
|
||||
POSTGRES_HOST=prod-db-host
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USER=prod_user
|
||||
POSTGRES_PASSWORD=secure_prod_password
|
||||
|
||||
# Next.js
|
||||
NEXT_PUBLIC_APP_URL=https://dre-gerencial.com
|
||||
NODE_ENV=production
|
||||
|
||||
# Security
|
||||
NEXTAUTH_SECRET=your-secret-key
|
||||
NEXTAUTH_URL=https://dre-gerencial.com
|
||||
```
|
||||
|
||||
### 2. **Configuração do Banco de Dados**
|
||||
|
||||
#### Desenvolvimento
|
||||
```bash
|
||||
# Criar banco de desenvolvimento
|
||||
createdb dre_gerencial
|
||||
|
||||
# Conectar e verificar
|
||||
psql -h localhost -U postgres -d dre_gerencial
|
||||
```
|
||||
|
||||
#### Produção
|
||||
```bash
|
||||
# Criar usuário e banco de produção
|
||||
sudo -u postgres psql
|
||||
CREATE USER prod_user WITH PASSWORD 'secure_prod_password';
|
||||
CREATE DATABASE dre_gerencial_prod OWNER prod_user;
|
||||
GRANT ALL PRIVILEGES ON DATABASE dre_gerencial_prod TO prod_user;
|
||||
\q
|
||||
```
|
||||
|
||||
## Deploy Local
|
||||
|
||||
### 1. **Desenvolvimento**
|
||||
```bash
|
||||
# Instalar dependências
|
||||
npm install
|
||||
|
||||
# Executar migrações
|
||||
npx drizzle-kit migrate
|
||||
|
||||
# Iniciar servidor de desenvolvimento
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 2. **Build de Produção Local**
|
||||
```bash
|
||||
# Build otimizado
|
||||
npm run build
|
||||
|
||||
# Iniciar servidor de produção
|
||||
npm run start
|
||||
```
|
||||
|
||||
## Deploy em Servidor
|
||||
|
||||
### 1. **Preparação do Servidor**
|
||||
|
||||
#### Instalar Node.js
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
# Verificar instalação
|
||||
node --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
#### Instalar PostgreSQL
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt update
|
||||
sudo apt install postgresql postgresql-contrib
|
||||
|
||||
# Iniciar serviço
|
||||
sudo systemctl start postgresql
|
||||
sudo systemctl enable postgresql
|
||||
```
|
||||
|
||||
#### Instalar PM2 (Gerenciador de Processos)
|
||||
```bash
|
||||
# Instalar PM2 globalmente
|
||||
sudo npm install -g pm2
|
||||
|
||||
# Configurar PM2 para iniciar com o sistema
|
||||
pm2 startup
|
||||
pm2 save
|
||||
```
|
||||
|
||||
### 2. **Deploy da Aplicação**
|
||||
|
||||
#### Clonar Repositório
|
||||
```bash
|
||||
# Criar diretório da aplicação
|
||||
sudo mkdir -p /var/www/dre-gerencial
|
||||
sudo chown $USER:$USER /var/www/dre-gerencial
|
||||
|
||||
# Clonar repositório
|
||||
cd /var/www/dre-gerencial
|
||||
git clone <repository-url> .
|
||||
|
||||
# Instalar dependências
|
||||
npm install --production
|
||||
```
|
||||
|
||||
#### Configurar Variáveis de Ambiente
|
||||
```bash
|
||||
# Criar arquivo de ambiente de produção
|
||||
cp .env.example .env.production
|
||||
|
||||
# Editar variáveis
|
||||
nano .env.production
|
||||
```
|
||||
|
||||
#### Build da Aplicação
|
||||
```bash
|
||||
# Build para produção
|
||||
npm run build
|
||||
|
||||
# Verificar se build foi bem-sucedido
|
||||
ls -la .next/
|
||||
```
|
||||
|
||||
#### Configurar PM2
|
||||
```bash
|
||||
# Criar arquivo de configuração PM2
|
||||
cat > ecosystem.config.js << EOF
|
||||
module.exports = {
|
||||
apps: [{
|
||||
name: 'dre-gerencial',
|
||||
script: 'npm',
|
||||
args: 'start',
|
||||
cwd: '/var/www/dre-gerencial',
|
||||
instances: 1,
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: '1G',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: 3000
|
||||
}
|
||||
}]
|
||||
};
|
||||
EOF
|
||||
|
||||
# Iniciar aplicação
|
||||
pm2 start ecosystem.config.js
|
||||
|
||||
# Verificar status
|
||||
pm2 status
|
||||
pm2 logs dre-gerencial
|
||||
```
|
||||
|
||||
### 3. **Configuração do Nginx (Opcional)**
|
||||
|
||||
#### Instalar Nginx
|
||||
```bash
|
||||
sudo apt install nginx
|
||||
sudo systemctl start nginx
|
||||
sudo systemctl enable nginx
|
||||
```
|
||||
|
||||
#### Configurar Proxy Reverso
|
||||
```bash
|
||||
# Criar configuração do site
|
||||
sudo nano /etc/nginx/sites-available/dre-gerencial
|
||||
|
||||
# Conteúdo do arquivo
|
||||
server {
|
||||
listen 80;
|
||||
server_name dre-gerencial.com www.dre-gerencial.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
# Habilitar site
|
||||
sudo ln -s /etc/nginx/sites-available/dre-gerencial /etc/nginx/sites-enabled/
|
||||
|
||||
# Testar configuração
|
||||
sudo nginx -t
|
||||
|
||||
# Recarregar Nginx
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
#### Configurar SSL com Let's Encrypt
|
||||
```bash
|
||||
# Instalar Certbot
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
|
||||
# Obter certificado SSL
|
||||
sudo certbot --nginx -d dre-gerencial.com -d www.dre-gerencial.com
|
||||
|
||||
# Verificar renovação automática
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
## Deploy com Docker
|
||||
|
||||
### 1. **Dockerfile**
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
### 2. **Docker Compose**
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- POSTGRES_DB=dre_gerencial
|
||||
- POSTGRES_HOST=db
|
||||
- POSTGRES_PORT=5432
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=password
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
image: postgres:13
|
||||
environment:
|
||||
- POSTGRES_DB=dre_gerencial
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
### 3. **Deploy com Docker**
|
||||
```bash
|
||||
# Build e executar
|
||||
docker-compose up -d
|
||||
|
||||
# Verificar logs
|
||||
docker-compose logs -f app
|
||||
|
||||
# Parar serviços
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
## Deploy Automatizado
|
||||
|
||||
### 1. **GitHub Actions**
|
||||
|
||||
#### Workflow de Deploy
|
||||
```yaml
|
||||
# .github/workflows/deploy.yml
|
||||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Build application
|
||||
run: npm run build
|
||||
|
||||
- name: Deploy to server
|
||||
uses: appleboy/ssh-action@v0.1.5
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
script: |
|
||||
cd /var/www/dre-gerencial
|
||||
git pull origin main
|
||||
npm ci --production
|
||||
npm run build
|
||||
pm2 restart dre-gerencial
|
||||
```
|
||||
|
||||
### 2. **Configuração de Secrets**
|
||||
No GitHub, adicionar os seguintes secrets:
|
||||
- `HOST`: IP do servidor
|
||||
- `USERNAME`: usuário do servidor
|
||||
- `SSH_KEY`: chave SSH privada
|
||||
|
||||
## Monitoramento
|
||||
|
||||
### 1. **PM2 Monitoring**
|
||||
```bash
|
||||
# Monitorar aplicação
|
||||
pm2 monit
|
||||
|
||||
# Ver logs em tempo real
|
||||
pm2 logs dre-gerencial --lines 100
|
||||
|
||||
# Reiniciar aplicação
|
||||
pm2 restart dre-gerencial
|
||||
|
||||
# Parar aplicação
|
||||
pm2 stop dre-gerencial
|
||||
```
|
||||
|
||||
### 2. **Logs do Sistema**
|
||||
```bash
|
||||
# Logs do Nginx
|
||||
sudo tail -f /var/log/nginx/access.log
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
|
||||
# Logs do PostgreSQL
|
||||
sudo tail -f /var/log/postgresql/postgresql-13-main.log
|
||||
```
|
||||
|
||||
### 3. **Monitoramento de Recursos**
|
||||
```bash
|
||||
# Uso de CPU e memória
|
||||
htop
|
||||
|
||||
# Espaço em disco
|
||||
df -h
|
||||
|
||||
# Status dos serviços
|
||||
systemctl status nginx
|
||||
systemctl status postgresql
|
||||
```
|
||||
|
||||
## Backup e Restore
|
||||
|
||||
### 1. **Backup do Banco de Dados**
|
||||
```bash
|
||||
# Backup completo
|
||||
pg_dump -h localhost -U postgres -d dre_gerencial_prod > backup_$(date +%Y%m%d).sql
|
||||
|
||||
# Backup apenas dados
|
||||
pg_dump -h localhost -U postgres -d dre_gerencial_prod --data-only > data_backup_$(date +%Y%m%d).sql
|
||||
|
||||
# Backup apenas schema
|
||||
pg_dump -h localhost -U postgres -d dre_gerencial_prod --schema-only > schema_backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
|
||||
### 2. **Restore do Banco de Dados**
|
||||
```bash
|
||||
# Restore completo
|
||||
psql -h localhost -U postgres -d dre_gerencial_prod < backup_20240101.sql
|
||||
|
||||
# Restore apenas dados
|
||||
psql -h localhost -U postgres -d dre_gerencial_prod < data_backup_20240101.sql
|
||||
```
|
||||
|
||||
### 3. **Backup Automático**
|
||||
```bash
|
||||
# Criar script de backup
|
||||
cat > /home/user/backup_dre.sh << EOF
|
||||
#!/bin/bash
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_DIR="/home/user/backups"
|
||||
DB_NAME="dre_gerencial_prod"
|
||||
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# Backup do banco
|
||||
pg_dump -h localhost -U postgres -d $DB_NAME > $BACKUP_DIR/dre_backup_$DATE.sql
|
||||
|
||||
# Manter apenas os últimos 7 backups
|
||||
cd $BACKUP_DIR
|
||||
ls -t dre_backup_*.sql | tail -n +8 | xargs -r rm
|
||||
|
||||
echo "Backup completed: dre_backup_$DATE.sql"
|
||||
EOF
|
||||
|
||||
# Tornar executável
|
||||
chmod +x /home/user/backup_dre.sh
|
||||
|
||||
# Agendar backup diário
|
||||
crontab -e
|
||||
# Adicionar linha:
|
||||
0 2 * * * /home/user/backup_dre.sh
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 1. **Problemas Comuns**
|
||||
|
||||
#### Aplicação não inicia
|
||||
```bash
|
||||
# Verificar logs
|
||||
pm2 logs dre-gerencial
|
||||
|
||||
# Verificar se porta está em uso
|
||||
netstat -tlnp | grep :3000
|
||||
|
||||
# Verificar variáveis de ambiente
|
||||
pm2 env 0
|
||||
```
|
||||
|
||||
#### Erro de conexão com banco
|
||||
```bash
|
||||
# Verificar se PostgreSQL está rodando
|
||||
sudo systemctl status postgresql
|
||||
|
||||
# Testar conexão
|
||||
psql -h localhost -U postgres -d dre_gerencial_prod
|
||||
|
||||
# Verificar logs do PostgreSQL
|
||||
sudo tail -f /var/log/postgresql/postgresql-13-main.log
|
||||
```
|
||||
|
||||
#### Erro de permissões
|
||||
```bash
|
||||
# Verificar permissões do diretório
|
||||
ls -la /var/www/dre-gerencial
|
||||
|
||||
# Corrigir permissões
|
||||
sudo chown -R $USER:$USER /var/www/dre-gerencial
|
||||
chmod -R 755 /var/www/dre-gerencial
|
||||
```
|
||||
|
||||
### 2. **Comandos Úteis**
|
||||
|
||||
#### Reiniciar serviços
|
||||
```bash
|
||||
# Reiniciar aplicação
|
||||
pm2 restart dre-gerencial
|
||||
|
||||
# Reiniciar Nginx
|
||||
sudo systemctl restart nginx
|
||||
|
||||
# Reiniciar PostgreSQL
|
||||
sudo systemctl restart postgresql
|
||||
```
|
||||
|
||||
#### Verificar status
|
||||
```bash
|
||||
# Status da aplicação
|
||||
pm2 status
|
||||
|
||||
# Status dos serviços do sistema
|
||||
sudo systemctl status nginx postgresql
|
||||
|
||||
# Uso de recursos
|
||||
free -h
|
||||
df -h
|
||||
```
|
||||
|
||||
## Próximos Passos
|
||||
|
||||
1. **Implementar CI/CD** completo
|
||||
2. **Adicionar monitoramento** com Prometheus/Grafana
|
||||
3. **Implementar backup** automatizado
|
||||
4. **Adicionar alertas** por email/Slack
|
||||
5. **Implementar load balancing** para alta disponibilidade
|
||||
6. **Adicionar CDN** para assets estáticos
|
||||
7. **Implementar cache** com Redis
|
||||
8. **Adicionar health checks** automatizados
|
||||
|
|
@ -0,0 +1,529 @@
|
|||
# Guia de Desenvolvimento - DRE Gerencial
|
||||
|
||||
## Configuração do Ambiente
|
||||
|
||||
### 1. **Pré-requisitos**
|
||||
- Node.js 18+
|
||||
- PostgreSQL 13+
|
||||
- npm ou yarn
|
||||
- Git
|
||||
|
||||
### 2. **Instalação**
|
||||
```bash
|
||||
# Clone o repositório
|
||||
git clone <repository-url>
|
||||
cd dre-modelo
|
||||
|
||||
# Instale as dependências
|
||||
npm install
|
||||
|
||||
# Configure as variáveis de ambiente
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
### 3. **Variáveis de Ambiente**
|
||||
```env
|
||||
# Database
|
||||
POSTGRES_DB=dre_gerencial
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=sua_senha
|
||||
|
||||
# Next.js
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
```
|
||||
|
||||
### 4. **Configuração do Banco**
|
||||
```bash
|
||||
# Criar banco de dados
|
||||
createdb dre_gerencial
|
||||
|
||||
# Executar migrações (se houver)
|
||||
npx drizzle-kit migrate
|
||||
```
|
||||
|
||||
## Scripts Disponíveis
|
||||
|
||||
### 1. **Desenvolvimento**
|
||||
```bash
|
||||
# Iniciar servidor de desenvolvimento
|
||||
npm run dev
|
||||
|
||||
# Build para produção
|
||||
npm run build
|
||||
|
||||
# Iniciar servidor de produção
|
||||
npm run start
|
||||
|
||||
# Linting
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### 2. **Banco de Dados**
|
||||
```bash
|
||||
# Gerar migração
|
||||
npx drizzle-kit generate
|
||||
|
||||
# Aplicar migração
|
||||
npx drizzle-kit migrate
|
||||
|
||||
# Visualizar schema
|
||||
npx drizzle-kit studio
|
||||
```
|
||||
|
||||
## Estrutura do Projeto
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/ # Next.js App Router
|
||||
│ ├── api/ # API Routes
|
||||
│ │ ├── analitico/ # API analítica
|
||||
│ │ └── dre/ # API DRE
|
||||
│ ├── DRE/ # Páginas DRE
|
||||
│ │ ├── analitico.tsx # Componente analítico
|
||||
│ │ ├── page.tsx # Página principal
|
||||
│ │ └── teste.tsx # Componente principal
|
||||
│ ├── globals.css # Estilos globais
|
||||
│ └── layout.tsx # Layout raiz
|
||||
├── components/ # Componentes reutilizáveis
|
||||
│ └── ui/ # Componentes UI base
|
||||
├── db/ # Configuração do banco
|
||||
│ ├── index.ts # Conexão Drizzle
|
||||
│ └── schema.ts # Schema do banco
|
||||
└── lib/ # Utilitários
|
||||
└── utils.ts # Funções utilitárias
|
||||
```
|
||||
|
||||
## Padrões de Código
|
||||
|
||||
### 1. **TypeScript**
|
||||
- Sempre usar tipos explícitos
|
||||
- Interfaces para props de componentes
|
||||
- Tipos específicos para dados de API
|
||||
|
||||
```typescript
|
||||
// ✅ Bom
|
||||
interface AnaliticoItem {
|
||||
id: number;
|
||||
valor: number;
|
||||
data_competencia: string;
|
||||
}
|
||||
|
||||
// ❌ Evitar
|
||||
const data: any = await response.json();
|
||||
```
|
||||
|
||||
### 2. **React Hooks**
|
||||
- Usar `useCallback` para funções passadas como props
|
||||
- Usar `useMemo` para cálculos pesados
|
||||
- Evitar dependências desnecessárias em `useEffect`
|
||||
|
||||
```typescript
|
||||
// ✅ Bom
|
||||
const fetchData = useCallback(async () => {
|
||||
// lógica de fetch
|
||||
}, [filtros]);
|
||||
|
||||
// ❌ Evitar
|
||||
const fetchData = async () => {
|
||||
// lógica de fetch
|
||||
};
|
||||
```
|
||||
|
||||
### 3. **Styling**
|
||||
- Usar Tailwind CSS para styling
|
||||
- Classes utilitárias para responsividade
|
||||
- Variantes com class-variance-authority
|
||||
|
||||
```typescript
|
||||
// ✅ Bom
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground',
|
||||
outline: 'border border-input bg-background',
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// ❌ Evitar
|
||||
const styles = {
|
||||
button: 'bg-blue-500 text-white px-4 py-2',
|
||||
};
|
||||
```
|
||||
|
||||
## Desenvolvimento de Novas Funcionalidades
|
||||
|
||||
### 1. **Adicionando Nova API**
|
||||
|
||||
#### Criar arquivo de rota
|
||||
```typescript
|
||||
// src/app/api/nova-funcionalidade/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import db from '@/db';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Lógica da API
|
||||
const data = await db.execute(sql`SELECT * FROM tabela`);
|
||||
|
||||
return NextResponse.json(data.rows);
|
||||
} catch (error) {
|
||||
console.error('Erro:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Erro interno' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Adicionar tipos
|
||||
```typescript
|
||||
// src/types/nova-funcionalidade.ts
|
||||
export interface NovaFuncionalidadeItem {
|
||||
id: number;
|
||||
nome: string;
|
||||
valor: number;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **Adicionando Novo Componente**
|
||||
|
||||
#### Estrutura do componente
|
||||
```typescript
|
||||
// src/components/NovaFuncionalidade.tsx
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
interface NovaFuncionalidadeProps {
|
||||
filtros: {
|
||||
dataInicio: string;
|
||||
dataFim: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function NovaFuncionalidade({ filtros }: NovaFuncionalidadeProps) {
|
||||
const [data, setData] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(`/api/nova-funcionalidade?${new URLSearchParams(filtros)}`);
|
||||
const result = await response.json();
|
||||
setData(result);
|
||||
} catch (error) {
|
||||
console.error('Erro:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [filtros]);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<h2 className="text-lg font-bold mb-4">Nova Funcionalidade</h2>
|
||||
|
||||
{loading ? (
|
||||
<div>Carregando...</div>
|
||||
) : (
|
||||
<div>
|
||||
{/* Renderizar dados */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **Adicionando Nova Página**
|
||||
|
||||
#### Criar página
|
||||
```typescript
|
||||
// src/app/nova-pagina/page.tsx
|
||||
import NovaFuncionalidade from '@/components/NovaFuncionalidade';
|
||||
|
||||
export default function NovaPagina() {
|
||||
return (
|
||||
<div className="w-full min-h-screen p-4">
|
||||
<NovaFuncionalidade filtros={{ dataInicio: '', dataFim: '' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### 1. **Logs de Desenvolvimento**
|
||||
```typescript
|
||||
// Usar console.log para debugging
|
||||
console.log('Dados recebidos:', data);
|
||||
|
||||
// Logs estruturados
|
||||
console.log({
|
||||
timestamp: new Date().toISOString(),
|
||||
component: 'AnaliticoComponent',
|
||||
action: 'fetchData',
|
||||
data: data.length
|
||||
});
|
||||
```
|
||||
|
||||
### 2. **React Developer Tools**
|
||||
- Instalar extensão do Chrome/Firefox
|
||||
- Inspecionar estado dos componentes
|
||||
- Profiler para performance
|
||||
|
||||
### 3. **Network Tab**
|
||||
- Verificar requisições de API
|
||||
- Analisar tempo de resposta
|
||||
- Verificar payloads
|
||||
|
||||
## Testes
|
||||
|
||||
### 1. **Configuração de Testes**
|
||||
```bash
|
||||
# Instalar dependências de teste
|
||||
npm install --save-dev @testing-library/react @testing-library/jest-dom jest jest-environment-jsdom
|
||||
|
||||
# Configurar Jest
|
||||
# jest.config.js
|
||||
const nextJest = require('next/jest');
|
||||
|
||||
const createJestConfig = nextJest({
|
||||
dir: './',
|
||||
});
|
||||
|
||||
const customJestConfig = {
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
||||
moduleNameMapping: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
},
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
};
|
||||
|
||||
module.exports = createJestConfig(customJestConfig);
|
||||
```
|
||||
|
||||
### 2. **Testes Unitários**
|
||||
```typescript
|
||||
// __tests__/components/Analitico.test.tsx
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import AnaliticoComponent from '@/app/DRE/analitico';
|
||||
|
||||
describe('AnaliticoComponent', () => {
|
||||
it('renders without crashing', () => {
|
||||
const filtros = {
|
||||
dataInicio: '2024-01',
|
||||
dataFim: '2024-12',
|
||||
};
|
||||
|
||||
render(<AnaliticoComponent filtros={filtros} />);
|
||||
expect(screen.getByText('Análise Analítica')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 3. **Testes de API**
|
||||
```typescript
|
||||
// __tests__/api/analitico.test.ts
|
||||
import { GET } from '@/app/api/analitico/route';
|
||||
|
||||
describe('/api/analitico', () => {
|
||||
it('returns data for valid parameters', async () => {
|
||||
const request = new Request('http://localhost:3000/api/analitico?dataInicio=2024-01&dataFim=2024-12');
|
||||
const response = await GET(request);
|
||||
const data = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(Array.isArray(data)).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### 1. **Otimizações de Bundle**
|
||||
```typescript
|
||||
// Lazy loading de componentes
|
||||
const AnaliticoComponent = lazy(() => import('./analitico'));
|
||||
|
||||
// Dynamic imports
|
||||
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
|
||||
loading: () => <div>Carregando...</div>,
|
||||
});
|
||||
```
|
||||
|
||||
### 2. **Otimizações de Renderização**
|
||||
```typescript
|
||||
// Memoização de componentes
|
||||
const MemoizedComponent = memo(({ data }) => {
|
||||
return <div>{data.map(item => <Item key={item.id} data={item} />)}</div>;
|
||||
});
|
||||
|
||||
// Memoização de cálculos
|
||||
const expensiveValue = useMemo(() => {
|
||||
return data.reduce((sum, item) => sum + item.valor, 0);
|
||||
}, [data]);
|
||||
```
|
||||
|
||||
### 3. **Otimizações de API**
|
||||
```typescript
|
||||
// Cache de dados
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['analitico', filtros],
|
||||
queryFn: () => fetchAnaliticoData(filtros),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutos
|
||||
});
|
||||
```
|
||||
|
||||
## Deploy
|
||||
|
||||
### 1. **Build de Produção**
|
||||
```bash
|
||||
# Build otimizado
|
||||
npm run build
|
||||
|
||||
# Verificar build
|
||||
npm run start
|
||||
```
|
||||
|
||||
### 2. **Variáveis de Ambiente de Produção**
|
||||
```env
|
||||
# Produção
|
||||
POSTGRES_DB=dre_gerencial_prod
|
||||
POSTGRES_HOST=prod-db-host
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USER=prod_user
|
||||
POSTGRES_PASSWORD=prod_password
|
||||
|
||||
NEXT_PUBLIC_APP_URL=https://dre-gerencial.com
|
||||
```
|
||||
|
||||
### 3. **Docker (Opcional)**
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 1. **Problemas Comuns**
|
||||
|
||||
#### Erro de Conexão com Banco
|
||||
```bash
|
||||
# Verificar se PostgreSQL está rodando
|
||||
pg_ctl status
|
||||
|
||||
# Verificar conexão
|
||||
psql -h localhost -U postgres -d dre_gerencial
|
||||
```
|
||||
|
||||
#### Erro de Build
|
||||
```bash
|
||||
# Limpar cache
|
||||
rm -rf .next node_modules
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
#### Erro de TypeScript
|
||||
```bash
|
||||
# Verificar tipos
|
||||
npx tsc --noEmit
|
||||
|
||||
# Atualizar tipos
|
||||
npm update @types/react @types/react-dom
|
||||
```
|
||||
|
||||
### 2. **Logs de Erro**
|
||||
```typescript
|
||||
// Error boundary
|
||||
class ErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
console.error('Error caught by boundary:', error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return <div>Algo deu errado.</div>;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Contribuição
|
||||
|
||||
### 1. **Fluxo de Trabalho**
|
||||
```bash
|
||||
# Criar branch
|
||||
git checkout -b feature/nova-funcionalidade
|
||||
|
||||
# Fazer commits
|
||||
git add .
|
||||
git commit -m "feat: adiciona nova funcionalidade"
|
||||
|
||||
# Push
|
||||
git push origin feature/nova-funcionalidade
|
||||
|
||||
# Criar Pull Request
|
||||
```
|
||||
|
||||
### 2. **Padrões de Commit**
|
||||
```
|
||||
feat: nova funcionalidade
|
||||
fix: correção de bug
|
||||
docs: atualização de documentação
|
||||
style: formatação de código
|
||||
refactor: refatoração de código
|
||||
test: adição de testes
|
||||
chore: tarefas de manutenção
|
||||
```
|
||||
|
||||
### 3. **Code Review**
|
||||
- Verificar tipos TypeScript
|
||||
- Testar funcionalidades
|
||||
- Validar performance
|
||||
- Verificar acessibilidade
|
||||
- Revisar documentação
|
||||
|
||||
## Próximos Passos
|
||||
|
||||
1. **Implementar CI/CD** com GitHub Actions
|
||||
2. **Adicionar testes E2E** com Playwright
|
||||
3. **Implementar monitoramento** com Sentry
|
||||
4. **Adicionar Storybook** para componentes
|
||||
5. **Implementar PWA** para mobile
|
||||
6. **Adicionar internacionalização** (i18n)
|
||||
7. **Implementar cache** com Redis
|
||||
8. **Adicionar métricas** com Analytics
|
||||
|
|
@ -0,0 +1,603 @@
|
|||
# Troubleshooting - DRE Gerencial
|
||||
|
||||
## Visão Geral
|
||||
|
||||
Este guia contém soluções para problemas comuns encontrados durante o desenvolvimento, deploy e manutenção do sistema DRE Gerencial.
|
||||
|
||||
## Problemas de Desenvolvimento
|
||||
|
||||
### 1. **Erros de TypeScript**
|
||||
|
||||
#### Erro: "Cannot find module '@/components/ui/button'"
|
||||
```bash
|
||||
# Verificar se o arquivo existe
|
||||
ls -la src/components/ui/button.tsx
|
||||
|
||||
# Verificar tsconfig.json
|
||||
cat tsconfig.json | grep paths
|
||||
|
||||
# Solução: Verificar se o path alias está correto
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Erro: "Property 'valor' does not exist on type 'DREItem'"
|
||||
```typescript
|
||||
// Verificar interface
|
||||
interface DREItem {
|
||||
valor: string; // ou number
|
||||
// outros campos...
|
||||
}
|
||||
|
||||
// Solução: Verificar se o tipo está correto na API
|
||||
const valor = typeof item.valor === 'string' ? parseFloat(item.valor) : item.valor;
|
||||
```
|
||||
|
||||
### 2. **Erros de Build**
|
||||
|
||||
#### Erro: "Module not found: Can't resolve 'xlsx'"
|
||||
```bash
|
||||
# Instalar dependência
|
||||
npm install xlsx
|
||||
npm install --save-dev @types/xlsx
|
||||
|
||||
# Verificar se está no package.json
|
||||
cat package.json | grep xlsx
|
||||
```
|
||||
|
||||
#### Erro: "Failed to compile"
|
||||
```bash
|
||||
# Limpar cache
|
||||
rm -rf .next node_modules
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# Verificar se há erros de TypeScript
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
### 3. **Problemas de Performance**
|
||||
|
||||
#### Componente renderizando muito
|
||||
```typescript
|
||||
// Solução: Usar useMemo para cálculos pesados
|
||||
const expensiveValue = useMemo(() => {
|
||||
return data.reduce((sum, item) => sum + item.valor, 0);
|
||||
}, [data]);
|
||||
|
||||
// Usar useCallback para funções
|
||||
const handleClick = useCallback((id: string) => {
|
||||
// lógica
|
||||
}, [dependencies]);
|
||||
```
|
||||
|
||||
#### Lista muito grande causando lag
|
||||
```typescript
|
||||
// Solução: Implementar virtualização
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
|
||||
const VirtualizedList = ({ items }) => (
|
||||
<List
|
||||
height={400}
|
||||
itemCount={items.length}
|
||||
itemSize={50}
|
||||
itemData={items}
|
||||
>
|
||||
{({ index, style, data }) => (
|
||||
<div style={style}>
|
||||
{data[index].name}
|
||||
</div>
|
||||
)}
|
||||
</List>
|
||||
);
|
||||
```
|
||||
|
||||
## Problemas de Banco de Dados
|
||||
|
||||
### 1. **Erros de Conexão**
|
||||
|
||||
#### Erro: "Connection refused"
|
||||
```bash
|
||||
# Verificar se PostgreSQL está rodando
|
||||
sudo systemctl status postgresql
|
||||
|
||||
# Iniciar PostgreSQL
|
||||
sudo systemctl start postgresql
|
||||
|
||||
# Verificar porta
|
||||
netstat -tlnp | grep 5432
|
||||
```
|
||||
|
||||
#### Erro: "Authentication failed"
|
||||
```bash
|
||||
# Verificar usuário e senha
|
||||
psql -h localhost -U postgres -d dre_gerencial
|
||||
|
||||
# Verificar pg_hba.conf
|
||||
sudo nano /etc/postgresql/13/main/pg_hba.conf
|
||||
|
||||
# Configuração recomendada
|
||||
local all postgres peer
|
||||
host all all 127.0.0.1/32 md5
|
||||
```
|
||||
|
||||
### 2. **Erros de Query**
|
||||
|
||||
#### Erro: "relation 'view_dre_gerencial' does not exist"
|
||||
```sql
|
||||
-- Verificar se a view existe
|
||||
\dv view_dre_gerencial
|
||||
|
||||
-- Criar view se não existir
|
||||
CREATE VIEW view_dre_gerencial AS
|
||||
SELECT
|
||||
codfilial,
|
||||
data_competencia,
|
||||
data_caixa,
|
||||
grupo,
|
||||
subgrupo,
|
||||
centro_custo,
|
||||
codigo_conta,
|
||||
conta,
|
||||
valor
|
||||
FROM fato_financeiro_analitico;
|
||||
```
|
||||
|
||||
#### Erro: "column 'valor' does not exist"
|
||||
```sql
|
||||
-- Verificar estrutura da tabela
|
||||
\d fato_financeiro_analitico
|
||||
|
||||
-- Verificar se a coluna existe
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'fato_financeiro_analitico';
|
||||
```
|
||||
|
||||
### 3. **Problemas de Performance**
|
||||
|
||||
#### Query muito lenta
|
||||
```sql
|
||||
-- Verificar plano de execução
|
||||
EXPLAIN ANALYZE SELECT * FROM fato_financeiro_analitico
|
||||
WHERE data_competencia BETWEEN '2024-01-01' AND '2024-12-31';
|
||||
|
||||
-- Criar índices necessários
|
||||
CREATE INDEX idx_fato_financeiro_data_competencia
|
||||
ON fato_financeiro_analitico (data_competencia);
|
||||
|
||||
CREATE INDEX idx_fato_financeiro_centro_custo
|
||||
ON fato_financeiro_analitico (codigo_centrocusto);
|
||||
```
|
||||
|
||||
#### Timeout de conexão
|
||||
```typescript
|
||||
// Aumentar timeout no pool de conexões
|
||||
const pool = new Pool({
|
||||
database: process.env.POSTGRES_DB,
|
||||
host: process.env.POSTGRES_HOST,
|
||||
port: Number(process.env.POSTGRES_PORT),
|
||||
user: process.env.POSTGRES_USER,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
connectionTimeoutMillis: 10000,
|
||||
idleTimeoutMillis: 30000,
|
||||
max: 20,
|
||||
});
|
||||
```
|
||||
|
||||
## Problemas de API
|
||||
|
||||
### 1. **Erros 404**
|
||||
|
||||
#### Rota não encontrada
|
||||
```typescript
|
||||
// Verificar se o arquivo de rota existe
|
||||
ls -la src/app/api/analitico/route.ts
|
||||
|
||||
// Verificar se a função está exportada
|
||||
export async function GET(request: NextRequest) {
|
||||
// implementação
|
||||
}
|
||||
```
|
||||
|
||||
#### Parâmetros obrigatórios não fornecidos
|
||||
```typescript
|
||||
// Verificar validação de parâmetros
|
||||
if (!dataInicio || !dataFim) {
|
||||
return NextResponse.json(
|
||||
{ message: 'Parâmetros obrigatórios: dataInicio, dataFim' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **Erros 500**
|
||||
|
||||
#### Erro interno do servidor
|
||||
```typescript
|
||||
// Adicionar logs detalhados
|
||||
try {
|
||||
const data = await db.execute(query);
|
||||
return NextResponse.json(data.rows);
|
||||
} catch (error) {
|
||||
console.error('Erro detalhado:', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
query: query.toString(),
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: 'Erro interno do servidor' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Erro de memória
|
||||
```bash
|
||||
# Verificar uso de memória
|
||||
free -h
|
||||
|
||||
# Aumentar limite de memória do Node.js
|
||||
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 3. **Problemas de CORS**
|
||||
|
||||
#### Erro: "Access to fetch at '...' from origin '...' has been blocked by CORS policy"
|
||||
```typescript
|
||||
// Adicionar headers CORS
|
||||
export async function GET(request: NextRequest) {
|
||||
const response = NextResponse.json(data);
|
||||
|
||||
response.headers.set('Access-Control-Allow-Origin', '*');
|
||||
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
|
||||
response.headers.set('Access-Control-Allow-Headers', 'Content-Type');
|
||||
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
## Problemas de Deploy
|
||||
|
||||
### 1. **Erros de Build em Produção**
|
||||
|
||||
#### Erro: "Module not found"
|
||||
```bash
|
||||
# Verificar se todas as dependências estão instaladas
|
||||
npm ci --production
|
||||
|
||||
# Verificar se não há dependências de desenvolvimento em produção
|
||||
npm list --production
|
||||
```
|
||||
|
||||
#### Erro: "Out of memory"
|
||||
```bash
|
||||
# Aumentar memória para build
|
||||
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 2. **Problemas de PM2**
|
||||
|
||||
#### Aplicação não inicia
|
||||
```bash
|
||||
# Verificar logs
|
||||
pm2 logs dre-gerencial
|
||||
|
||||
# Verificar configuração
|
||||
pm2 show dre-gerencial
|
||||
|
||||
# Reiniciar aplicação
|
||||
pm2 restart dre-gerencial
|
||||
```
|
||||
|
||||
#### Aplicação reinicia constantemente
|
||||
```bash
|
||||
# Verificar uso de memória
|
||||
pm2 monit
|
||||
|
||||
# Ajustar limite de memória
|
||||
pm2 start ecosystem.config.js --max-memory-restart 1G
|
||||
```
|
||||
|
||||
### 3. **Problemas de Nginx**
|
||||
|
||||
#### Erro: "502 Bad Gateway"
|
||||
```bash
|
||||
# Verificar se a aplicação está rodando
|
||||
pm2 status
|
||||
|
||||
# Verificar logs do Nginx
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
|
||||
# Testar configuração
|
||||
sudo nginx -t
|
||||
```
|
||||
|
||||
#### Erro: "Connection refused"
|
||||
```bash
|
||||
# Verificar se a aplicação está escutando na porta correta
|
||||
netstat -tlnp | grep 3000
|
||||
|
||||
# Verificar configuração do proxy
|
||||
sudo nano /etc/nginx/sites-available/dre-gerencial
|
||||
```
|
||||
|
||||
## Problemas de Dados
|
||||
|
||||
### 1. **Dados não aparecem**
|
||||
|
||||
#### Verificar se há dados no banco
|
||||
```sql
|
||||
-- Verificar se há dados na tabela
|
||||
SELECT COUNT(*) FROM fato_financeiro_analitico;
|
||||
|
||||
-- Verificar se há dados na view
|
||||
SELECT COUNT(*) FROM view_dre_gerencial;
|
||||
|
||||
-- Verificar dados por período
|
||||
SELECT COUNT(*) FROM fato_financeiro_analitico
|
||||
WHERE data_competencia BETWEEN '2024-01-01' AND '2024-12-31';
|
||||
```
|
||||
|
||||
#### Verificar filtros aplicados
|
||||
```typescript
|
||||
// Adicionar logs para debug
|
||||
console.log('Filtros aplicados:', filtros);
|
||||
console.log('Query executada:', query.toString());
|
||||
console.log('Resultado:', data.length);
|
||||
```
|
||||
|
||||
### 2. **Dados incorretos**
|
||||
|
||||
#### Verificar cálculos
|
||||
```typescript
|
||||
// Verificar se os cálculos estão corretos
|
||||
const totalValor = data.reduce((sum, item) => {
|
||||
const valor = typeof item.valor === 'string' ? parseFloat(item.valor) : item.valor;
|
||||
return sum + (isNaN(valor) ? 0 : valor);
|
||||
}, 0);
|
||||
|
||||
console.log('Total calculado:', totalValor);
|
||||
```
|
||||
|
||||
#### Verificar formatação de datas
|
||||
```typescript
|
||||
// Verificar se as datas estão sendo formatadas corretamente
|
||||
const formatDate = (dateString: string) => {
|
||||
try {
|
||||
return new Date(dateString).toLocaleDateString('pt-BR');
|
||||
} catch (error) {
|
||||
console.error('Erro ao formatar data:', dateString, error);
|
||||
return 'Data inválida';
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. **Performance de Dados**
|
||||
|
||||
#### Query muito lenta
|
||||
```sql
|
||||
-- Verificar plano de execução
|
||||
EXPLAIN ANALYZE SELECT * FROM fato_financeiro_analitico
|
||||
WHERE data_competencia BETWEEN '2024-01-01' AND '2024-12-31'
|
||||
AND codigo_centrocusto = '001';
|
||||
|
||||
-- Criar índices compostos
|
||||
CREATE INDEX idx_fato_financeiro_composto
|
||||
ON fato_financeiro_analitico (data_competencia, codigo_centrocusto);
|
||||
```
|
||||
|
||||
#### Muitos dados carregando
|
||||
```typescript
|
||||
// Implementar paginação
|
||||
const ITEMS_PER_PAGE = 100;
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const paginatedData = data.slice(
|
||||
(currentPage - 1) * ITEMS_PER_PAGE,
|
||||
currentPage * ITEMS_PER_PAGE
|
||||
);
|
||||
```
|
||||
|
||||
## Problemas de Interface
|
||||
|
||||
### 1. **Componentes não renderizam**
|
||||
|
||||
#### Verificar se o componente está sendo importado corretamente
|
||||
```typescript
|
||||
// Verificar import
|
||||
import AnaliticoComponent from './analitico';
|
||||
|
||||
// Verificar se está sendo usado
|
||||
{!loading && data.length > 0 && (
|
||||
<AnaliticoComponent filtros={analiticoFiltros} />
|
||||
)}
|
||||
```
|
||||
|
||||
#### Verificar se há erros no console
|
||||
```typescript
|
||||
// Adicionar error boundary
|
||||
class ErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
console.error('Erro no componente:', error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return <div>Erro ao carregar componente</div>;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **Problemas de Styling**
|
||||
|
||||
#### Classes Tailwind não aplicadas
|
||||
```bash
|
||||
# Verificar se Tailwind está configurado
|
||||
cat tailwind.config.js
|
||||
|
||||
# Verificar se as classes estão sendo incluídas
|
||||
npm run build
|
||||
```
|
||||
|
||||
#### Componentes não responsivos
|
||||
```typescript
|
||||
// Verificar classes responsivas
|
||||
<div className="w-full md:w-1/2 lg:w-1/3">
|
||||
{/* conteúdo */}
|
||||
</div>
|
||||
```
|
||||
|
||||
### 3. **Problemas de Interação**
|
||||
|
||||
#### Cliques não funcionam
|
||||
```typescript
|
||||
// Verificar se o evento está sendo capturado
|
||||
const handleClick = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
console.log('Clique capturado');
|
||||
// lógica
|
||||
};
|
||||
|
||||
// Verificar se o elemento está clicável
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className="cursor-pointer hover:bg-blue-50"
|
||||
>
|
||||
Clique aqui
|
||||
</button>
|
||||
```
|
||||
|
||||
#### Estados não atualizam
|
||||
```typescript
|
||||
// Verificar se o estado está sendo atualizado corretamente
|
||||
const [data, setData] = useState([]);
|
||||
|
||||
const updateData = (newData) => {
|
||||
console.log('Atualizando dados:', newData);
|
||||
setData(newData);
|
||||
};
|
||||
|
||||
// Verificar se o useEffect está sendo executado
|
||||
useEffect(() => {
|
||||
console.log('useEffect executado');
|
||||
fetchData();
|
||||
}, [dependencies]);
|
||||
```
|
||||
|
||||
## Comandos Úteis para Debug
|
||||
|
||||
### 1. **Verificar Status do Sistema**
|
||||
```bash
|
||||
# Status dos serviços
|
||||
sudo systemctl status nginx postgresql
|
||||
|
||||
# Uso de recursos
|
||||
htop
|
||||
free -h
|
||||
df -h
|
||||
|
||||
# Logs do sistema
|
||||
sudo journalctl -f
|
||||
```
|
||||
|
||||
### 2. **Verificar Aplicação**
|
||||
```bash
|
||||
# Status da aplicação
|
||||
pm2 status
|
||||
pm2 logs dre-gerencial
|
||||
|
||||
# Verificar processos
|
||||
ps aux | grep node
|
||||
|
||||
# Verificar portas
|
||||
netstat -tlnp | grep :3000
|
||||
```
|
||||
|
||||
### 3. **Verificar Banco de Dados**
|
||||
```bash
|
||||
# Conectar ao banco
|
||||
psql -h localhost -U postgres -d dre_gerencial
|
||||
|
||||
# Verificar conexões ativas
|
||||
SELECT * FROM pg_stat_activity;
|
||||
|
||||
# Verificar tamanho do banco
|
||||
SELECT pg_size_pretty(pg_database_size('dre_gerencial'));
|
||||
```
|
||||
|
||||
## Logs e Monitoramento
|
||||
|
||||
### 1. **Configurar Logs Detalhados**
|
||||
```typescript
|
||||
// Adicionar logs estruturados
|
||||
const logger = {
|
||||
info: (message: string, data?: any) => {
|
||||
console.log({
|
||||
timestamp: new Date().toISOString(),
|
||||
level: 'INFO',
|
||||
message,
|
||||
data,
|
||||
});
|
||||
},
|
||||
error: (message: string, error?: any) => {
|
||||
console.error({
|
||||
timestamp: new Date().toISOString(),
|
||||
level: 'ERROR',
|
||||
message,
|
||||
error: error?.message || error,
|
||||
stack: error?.stack,
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 2. **Monitoramento de Performance**
|
||||
```typescript
|
||||
// Adicionar métricas de performance
|
||||
const performanceMonitor = {
|
||||
start: (label: string) => {
|
||||
performance.mark(`${label}-start`);
|
||||
},
|
||||
end: (label: string) => {
|
||||
performance.mark(`${label}-end`);
|
||||
performance.measure(label, `${label}-start`, `${label}-end`);
|
||||
|
||||
const measure = performance.getEntriesByName(label)[0];
|
||||
console.log(`${label}: ${measure.duration}ms`);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Próximos Passos
|
||||
|
||||
1. **Implementar sistema de logs** centralizado
|
||||
2. **Adicionar monitoramento** em tempo real
|
||||
3. **Implementar alertas** automáticos
|
||||
4. **Criar dashboard** de saúde do sistema
|
||||
5. **Implementar backup** automatizado
|
||||
6. **Adicionar testes** de carga
|
||||
7. **Implementar rollback** automático
|
||||
8. **Criar documentação** de runbooks
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ['oracledb']
|
||||
},
|
||||
webpack: (config, { isServer }) => {
|
||||
if (isServer) {
|
||||
config.externals.push('oracledb');
|
||||
}
|
||||
return config;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
|
|
@ -9,31 +9,38 @@
|
|||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"drizzle-orm": "^0.44.6",
|
||||
"lucide-react": "^0.545.0",
|
||||
"next": "15.5.4",
|
||||
"next": "^14.2.33",
|
||||
"oracledb": "^6.9.0",
|
||||
"pg": "^8.16.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/pg": "^8.15.5",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@types/xlsx": "^0.0.35",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"drizzle-kit": "^0.31.5",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.5.4",
|
||||
"tailwindcss": "^4",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"tsx": "^4.20.6",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "^5"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
const config = {
|
||||
plugins: ["@tailwindcss/postcss"],
|
||||
};
|
||||
|
||||
export default config;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,123 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { executeOracleQuery } from '@/db/oracle';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
console.log('🔄 Buscando dados analíticos do Oracle...');
|
||||
console.log('📋 URL completa:', request.url);
|
||||
console.log('🔍 Query params:', request.nextUrl.searchParams.toString());
|
||||
console.log('📊 Headers:', Object.fromEntries(request.headers.entries()));
|
||||
|
||||
// Extrair parâmetros de query
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const dataInicio = searchParams.get('dataInicio');
|
||||
const dataFim = searchParams.get('dataFim');
|
||||
const centroCusto = searchParams.get('centroCusto');
|
||||
const codigoGrupo = searchParams.get('codigoGrupo');
|
||||
const codigoConta = searchParams.get('codigoConta');
|
||||
|
||||
console.log('🎯 Filtros recebidos:', {
|
||||
dataInicio,
|
||||
dataFim,
|
||||
centroCusto,
|
||||
codigoGrupo,
|
||||
codigoConta
|
||||
});
|
||||
|
||||
// Construir query SQL com filtros
|
||||
let sql = `SELECT * FROM DRE_RESULTADO_ANALITICO WHERE 1=1`;
|
||||
const params: any[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
// Filtro por período
|
||||
if (dataInicio && dataFim) {
|
||||
sql += ` AND ANOMESCOMP >= :${paramIndex} AND ANOMESCOMP <= :${paramIndex + 1}`;
|
||||
params.push(dataInicio, dataFim);
|
||||
paramIndex += 2;
|
||||
console.log('📅 Adicionando filtro de período:', dataInicio, 'a', dataFim);
|
||||
}
|
||||
|
||||
// Filtro por centro de custo
|
||||
if (centroCusto) {
|
||||
sql += ` AND CODIGOCENTROCUSTO = :${paramIndex}`;
|
||||
params.push(centroCusto);
|
||||
paramIndex++;
|
||||
console.log('🏢 Adicionando filtro de centro:', centroCusto);
|
||||
}
|
||||
|
||||
// Filtro por código do grupo
|
||||
if (codigoGrupo) {
|
||||
sql += ` AND CODGRUPO = :${paramIndex}`;
|
||||
params.push(codigoGrupo);
|
||||
paramIndex++;
|
||||
console.log('📊 Adicionando filtro de grupo:', codigoGrupo);
|
||||
}
|
||||
|
||||
// Filtro por código da conta
|
||||
if (codigoConta) {
|
||||
sql += ` AND CODCONTA = :${paramIndex}`;
|
||||
params.push(codigoConta);
|
||||
paramIndex++;
|
||||
console.log('💰 Adicionando filtro de conta:', codigoConta);
|
||||
}
|
||||
|
||||
sql += ` ORDER BY DTVENC, CODFORNEC, CODCONTA`;
|
||||
|
||||
console.log('🗄️ Query SQL final:', sql);
|
||||
console.log('📋 Parâmetros:', params);
|
||||
|
||||
const data = await executeOracleQuery(sql, params);
|
||||
|
||||
console.log('✅ Query executada com sucesso:', data.length, 'registros encontrados');
|
||||
console.log('📝 Primeiros 3 registros:', data.slice(0, 3));
|
||||
|
||||
// Transformar os dados do Oracle para o formato esperado pelo componente
|
||||
const transformedData = data.map((item: any) => ({
|
||||
codigo_grupo: item.CODGRUPO || "",
|
||||
codigo_subgrupo: "", // Não existe na tabela Oracle
|
||||
codigo_fornecedor: item.CODFORNEC || "",
|
||||
nome_fornecedor: item.FORNECEDOR || "",
|
||||
id: item.NUMLANC || 0,
|
||||
codfilial: "001", // Valor padrão
|
||||
recnum: item.NUMLANC || 0,
|
||||
data_competencia: item.ANOMESCOMP || "",
|
||||
data_vencimento: item.DTVENC ? new Date(item.DTVENC).toISOString().split('T')[0] : "",
|
||||
data_pagamento: "", // Não existe na tabela Oracle
|
||||
data_caixa: item.DTCAIXA ? new Date(item.DTCAIXA).toISOString().split('T')[0] : "",
|
||||
codigo_conta: item.CODCONTA || "",
|
||||
conta: item.CONTA || "",
|
||||
codigo_centrocusto: item.CODIGOCENTROCUSTO || "",
|
||||
valor: item.VLREALIZADO || 0,
|
||||
historico: item.HISTORICO || "",
|
||||
historico2: item.HISTORICO2 || "",
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
// Campos adicionais do Oracle
|
||||
entidade: item.ENTIDADE || "",
|
||||
tipo_parceiro: item.TIPOPARCEIRO || "",
|
||||
valor_previsto: item.VLPREVISTO || 0,
|
||||
valor_confirmado: item.VLCONFIRMADO || 0,
|
||||
valor_pago: item.VLPAGO || 0,
|
||||
numero_lancamento: item.NUMLANC || 0,
|
||||
ano_mes_comp: item.ANOMESCOMP || "",
|
||||
codgrupo: item.CODGRUPO || ""
|
||||
}));
|
||||
|
||||
console.log('🔄 Dados transformados:', transformedData.length, 'registros');
|
||||
console.log('📝 Primeiros 3 transformados:', transformedData.slice(0, 3));
|
||||
|
||||
return NextResponse.json(transformedData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erro ao buscar dados analíticos do Oracle:', error);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Erro desconhecido',
|
||||
details: error instanceof Error ? error.stack : undefined
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { executeOracleQuery } from '@/db/oracle';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
console.log('🔄 Buscando dados DRE do Oracle...');
|
||||
|
||||
// Query para buscar dados da tabela DRE_RESULTADO
|
||||
const sql = `SELECT * FROM DRE_RESULTADO ORDER BY CODGRUPO, CODCONTA, CODCENTROCUSTO`;
|
||||
|
||||
const data = await executeOracleQuery(sql);
|
||||
|
||||
console.log('✅ Query executada com sucesso:', data.length, 'registros encontrados');
|
||||
|
||||
// Transformar os dados do Oracle para o formato esperado pelo componente
|
||||
const transformedData = data.map((item: any) => {
|
||||
const codgrupo = item.CODGRUPO || "";
|
||||
const nomeGrupo = item.GRUPO || "";
|
||||
|
||||
return {
|
||||
codfilial: "001", // Valor padrão
|
||||
data_competencia: item.DATA || "2023-03", // Usar DATA do Oracle
|
||||
data_cai: item.DATA || "2023-03", // Usar DATA do Oracle
|
||||
grupo: codgrupo ? `${codgrupo} - ${nomeGrupo}` : nomeGrupo, // Concatenar código + nome
|
||||
subgrupo: item.CENTROCUSTO || "", // Usar CENTROCUSTO como subgrupo
|
||||
centro_custo: item.CODIGOCENTROCUSTO || "", // Usar CODIGOCENTROCUSTO
|
||||
codigo_conta: parseInt(item.CODCONTA) || 0, // Converter CODCONTA para número
|
||||
conta: item.CONTA || "", // Usar CONTA do Oracle
|
||||
valor: item.VALOR?.toString() || "0", // Converter VALOR para string
|
||||
codgrupo: codgrupo, // Adicionar código do grupo para cálculos
|
||||
};
|
||||
});
|
||||
|
||||
// Criar grupos calculados que não existem no Oracle
|
||||
const gruposCalculados = criarGruposCalculados(transformedData);
|
||||
|
||||
// Combinar dados originais com grupos calculados
|
||||
const dadosCompletos = [...transformedData, ...gruposCalculados];
|
||||
|
||||
return NextResponse.json(dadosCompletos);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erro ao buscar dados DRE do Oracle:', error);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Erro desconhecido',
|
||||
details: error instanceof Error ? error.stack : undefined
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function criarGruposCalculados(dados: any[]) {
|
||||
const gruposCalculados: any[] = [];
|
||||
|
||||
// Agrupar dados por mês para cálculos
|
||||
const dadosPorMes = dados.reduce((acc, item) => {
|
||||
const mes = item.data_competencia;
|
||||
if (!acc[mes]) acc[mes] = [];
|
||||
acc[mes].push(item);
|
||||
return acc;
|
||||
}, {} as Record<string, any[]>);
|
||||
|
||||
// Para cada mês, criar os grupos calculados
|
||||
Object.keys(dadosPorMes).forEach(mes => {
|
||||
const dadosMes = dadosPorMes[mes];
|
||||
|
||||
// Calcular valores por grupo usando código numérico
|
||||
const valoresPorGrupo = dadosMes.reduce((acc, item) => {
|
||||
const codgrupo = item.codgrupo;
|
||||
if (!acc[codgrupo]) acc[codgrupo] = 0;
|
||||
acc[codgrupo] += parseFloat(item.valor);
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
// 03 - Faturamento Líquido (01 + 02)
|
||||
const faturamentoBruto = valoresPorGrupo['01'] || 0;
|
||||
const devolucao = valoresPorGrupo['02'] || 0;
|
||||
const faturamentoLiquido = faturamentoBruto + devolucao;
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "03 - FATURAMENTO LÍQUIDO",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "FATURAMENTO LÍQUIDO",
|
||||
valor: faturamentoLiquido.toString(),
|
||||
codgrupo: "03",
|
||||
isCalculado: true
|
||||
});
|
||||
|
||||
// 05 - Lucro Bruto (03 + 04)
|
||||
const cmv = valoresPorGrupo['04'] || 0;
|
||||
const lucroBruto = faturamentoLiquido + cmv;
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "05 - LUCRO BRUTO",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "LUCRO BRUTO",
|
||||
valor: lucroBruto.toString(),
|
||||
codgrupo: "05",
|
||||
isCalculado: true
|
||||
});
|
||||
|
||||
// 07 - Margem Loja (05 + 06)
|
||||
const receitasGastosDiretos = valoresPorGrupo['06'] || 0;
|
||||
const margemLoja = lucroBruto + receitasGastosDiretos;
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "07 - MARGEM LOJA",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "MARGEM LOJA",
|
||||
valor: margemLoja.toString(),
|
||||
codgrupo: "07",
|
||||
isCalculado: true
|
||||
});
|
||||
|
||||
// 10 - Resultado Operacional (07 + 08 + 09)
|
||||
const verba = valoresPorGrupo['08'] || 0;
|
||||
const receitasGastosIndiretos = valoresPorGrupo['09'] || 0;
|
||||
const resultadoOperacional = margemLoja + verba + receitasGastosIndiretos;
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "10 - RESULTADO OPERACIONAL",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "RESULTADO OPERACIONAL",
|
||||
valor: resultadoOperacional.toString(),
|
||||
codgrupo: "10",
|
||||
isCalculado: true
|
||||
});
|
||||
|
||||
// 13 - Resultado Financeiro (11 + 12)
|
||||
const receitaFinanceira = valoresPorGrupo['11'] || 0;
|
||||
const despesaFinanceira = valoresPorGrupo['12'] || 0;
|
||||
const resultadoFinanceiro = receitaFinanceira + despesaFinanceira;
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "13 - RESULTADO FINANCEIRO",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "RESULTADO FINANCEIRO",
|
||||
valor: resultadoFinanceiro.toString(),
|
||||
codgrupo: "13",
|
||||
isCalculado: true
|
||||
});
|
||||
|
||||
// 18 - Outras Receitas / Despesas (14 + 15 + 16 + 17)
|
||||
const prejuizosPerdas = valoresPorGrupo['14'] || 0;
|
||||
const inativas = valoresPorGrupo['15'] || 0;
|
||||
const diretoria = valoresPorGrupo['16'] || 0;
|
||||
const lancamentosSemCC = valoresPorGrupo['17'] || 0;
|
||||
const outrasReceitasDespesas = prejuizosPerdas + inativas + diretoria + lancamentosSemCC;
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "18 - OUTRAS RECEITAS / DESPESAS",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "OUTRAS RECEITAS / DESPESAS",
|
||||
valor: outrasReceitasDespesas.toString(),
|
||||
codgrupo: "18",
|
||||
isCalculado: true
|
||||
});
|
||||
|
||||
// 19 - LAIR (10 + 13 + 18)
|
||||
const lair = resultadoOperacional + resultadoFinanceiro + outrasReceitasDespesas;
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "19 - LAIR",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "LAIR",
|
||||
valor: lair.toString(),
|
||||
codgrupo: "19",
|
||||
isCalculado: true
|
||||
});
|
||||
|
||||
// 20 - IR (se LAIR > 0 calcular 20% e resultado negativo, se não 0)
|
||||
const ir = lair > 0 ? -(lair * 0.20) : 0;
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "20 - IR",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "IMPOSTO DE RENDA",
|
||||
valor: ir.toString(),
|
||||
codgrupo: "20",
|
||||
isCalculado: true
|
||||
});
|
||||
|
||||
// 21 - CSLL (se LAIR > 0 calcular 9% e resultado negativo, se não 0)
|
||||
const csll = lair > 0 ? -(lair * 0.09) : 0;
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "21 - CSLL",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "CONTRIBUIÇÃO SOCIAL SOBRE LUCRO LÍQUIDO",
|
||||
valor: csll.toString(),
|
||||
codgrupo: "21",
|
||||
isCalculado: true
|
||||
});
|
||||
|
||||
// 22 - Lucro Líquido (19 + 20 + 21)
|
||||
const lucroLiquido = lair + ir + csll;
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "22 - LUCRO LÍQUIDO",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "LUCRO LÍQUIDO",
|
||||
valor: lucroLiquido.toString(),
|
||||
codgrupo: "22",
|
||||
isCalculado: true
|
||||
});
|
||||
|
||||
// 24 - EBITDA (19 - (13 + 18 + 23))
|
||||
const despesaTributaria = valoresPorGrupo['23'] || 0;
|
||||
const ebitda = lair - (resultadoFinanceiro + outrasReceitasDespesas + despesaTributaria);
|
||||
|
||||
gruposCalculados.push({
|
||||
codfilial: "001",
|
||||
data_competencia: mes,
|
||||
data_cai: mes,
|
||||
grupo: "24 - EBITDA",
|
||||
subgrupo: "CALCULADO",
|
||||
centro_custo: "CALCULADO",
|
||||
codigo_conta: 0,
|
||||
conta: "EBITDA",
|
||||
valor: ebitda.toString(),
|
||||
codgrupo: "24",
|
||||
isCalculado: true
|
||||
});
|
||||
});
|
||||
|
||||
return gruposCalculados;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { executeOracleQuery } from '@/db/oracle';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
console.log('🔄 Testando conexão Oracle...');
|
||||
|
||||
// Query de teste conforme solicitado
|
||||
const sql = `SELECT * FROM DRE_RESULTADO WHERE codgrupo = '08'`;
|
||||
|
||||
const data = await executeOracleQuery(sql);
|
||||
|
||||
console.log('✅ Query executada com sucesso:', data.length, 'registros encontrados');
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data,
|
||||
count: data.length,
|
||||
message: `Encontrados ${data.length} registros na tabela DRE_RESULTADO`
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Erro ao conectar com Oracle:', error);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Erro desconhecido',
|
||||
details: error instanceof Error ? error.stack : undefined
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +1,58 @@
|
|||
@import 'tailwindcss';
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--radius: 0.65rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
--radius: 0.5rem;
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
--muted: 210 40% 96%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import type { Metadata } from 'next';
|
||||
import { Geist, Geist_Mono } from 'next/font/google';
|
||||
import { Inter, JetBrains_Mono } from 'next/font/google';
|
||||
import './globals.css';
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: '--font-geist-sans',
|
||||
const inter = Inter({
|
||||
variable: '--font-inter',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: '--font-geist-mono',
|
||||
const jetbrainsMono = JetBrains_Mono({
|
||||
variable: '--font-jetbrains-mono',
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ export default function RootLayout({
|
|||
return (
|
||||
<html lang="pt-BR">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
className={`${inter.variable} ${jetbrainsMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
export { Card, CardContent }
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { X } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Dialog = DialogPrimitive.Root
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger
|
||||
|
||||
const DialogPortal = DialogPrimitive.Portal
|
||||
|
||||
const DialogClose = DialogPrimitive.Close
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogFooter.displayName = "DialogFooter"
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-white px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group
|
||||
|
||||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-white px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-white text-gray-900 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-gray-100 focus:text-gray-900 hover:bg-gray-50 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Sheet = SheetPrimitive.Root
|
||||
|
||||
const SheetTrigger = SheetPrimitive.Trigger
|
||||
|
||||
const SheetClose = SheetPrimitive.Close
|
||||
|
||||
const SheetPortal = SheetPrimitive.Portal
|
||||
|
||||
const SheetOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||
bottom:
|
||||
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||
right:
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
side: "right",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
interface SheetContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
SheetContentProps
|
||||
>(({ side = "right", className, children, ...props }, ref) => (
|
||||
<SheetPortal>
|
||||
<SheetOverlay />
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ side }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary z-50">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
))
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||
|
||||
const SheetHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetHeader.displayName = "SheetHeader"
|
||||
|
||||
const SheetFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetFooter.displayName = "SheetFooter"
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetPortal,
|
||||
SheetOverlay,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import { drizzle } from 'drizzle-orm/node-postgres';
|
|||
import { Pool } from 'pg';
|
||||
import * as schema from './schema';
|
||||
|
||||
// PostgreSQL Configuration
|
||||
const pool = new Pool({
|
||||
database: process.env.POSTGRES_DB,
|
||||
host: process.env.POSTGRES_HOST,
|
||||
|
|
@ -16,4 +17,6 @@ const db = drizzle({
|
|||
schema,
|
||||
});
|
||||
|
||||
// Export both PostgreSQL and Oracle connections
|
||||
export default db;
|
||||
export { executeOracleQuery, getOraclePool, closeOraclePool } from './oracle';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
import 'dotenv/config';
|
||||
import oracledb from 'oracledb';
|
||||
|
||||
// Configurar o caminho do Oracle Instant Client
|
||||
if (process.env.ORACLE_LIB_DIR) {
|
||||
oracledb.initOracleClient({ libDir: process.env.ORACLE_LIB_DIR });
|
||||
}
|
||||
|
||||
// Configuração do pool de conexões Oracle
|
||||
const oracleConfig = {
|
||||
user: process.env.ORACLE_USER,
|
||||
password: process.env.ORACLE_PASSWORD,
|
||||
connectString: process.env.ORACLE_CONNECTION_STRING,
|
||||
poolMin: Number(process.env.ORACLE_POOL_MIN) || 1,
|
||||
poolMax: Number(process.env.ORACLE_POOL_MAX) || 30,
|
||||
poolIncrement: Number(process.env.ORACLE_POOL_INCREMENT) || 1,
|
||||
queueTimeout: Number(process.env.ORACLE_QUEUE_TIMEOUT) || 60000,
|
||||
poolTimeout: Number(process.env.ORACLE_INACTIVITY_TIMEOUT) || 20000,
|
||||
};
|
||||
|
||||
// Criar pool de conexões
|
||||
let pool: oracledb.Pool | null = null;
|
||||
|
||||
export async function getOraclePool(): Promise<oracledb.Pool> {
|
||||
if (!pool) {
|
||||
try {
|
||||
pool = await oracledb.createPool(oracleConfig);
|
||||
console.log('✅ Pool Oracle criado com sucesso');
|
||||
} catch (error) {
|
||||
console.error('❌ Erro ao criar pool Oracle:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
export async function executeOracleQuery(sql: string, binds: any[] = []): Promise<any[]> {
|
||||
const pool = await getOraclePool();
|
||||
let connection: oracledb.Connection | null = null;
|
||||
|
||||
try {
|
||||
connection = await pool.getConnection();
|
||||
const result = await connection.execute(sql, binds, {
|
||||
outFormat: oracledb.OUT_FORMAT_OBJECT,
|
||||
});
|
||||
return result.rows || [];
|
||||
} catch (error) {
|
||||
console.error('❌ Erro ao executar query Oracle:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
if (connection) {
|
||||
await connection.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function closeOraclePool(): Promise<void> {
|
||||
if (pool) {
|
||||
await pool.close();
|
||||
pool = null;
|
||||
console.log('✅ Pool Oracle fechado');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getOraclePool,
|
||||
executeOracleQuery,
|
||||
closeOraclePool,
|
||||
};
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue