vendaweb-api/src/app/DRE/analitico.tsx

294 lines
9.2 KiB
TypeScript
Raw Normal View History

'use client';
import { Button } from '@/components/ui/button';
import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
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;
}
type SortField = 'nome_fornecedor' | 'data_competencia' | 'valor' | 'conta';
type SortDirection = 'asc' | 'desc';
interface SortConfig {
field: SortField;
direction: SortDirection;
}
interface AnaliticoProps {
filtros: {
dataInicio: string;
dataFim: string;
centroCusto?: string;
codigoGrupo?: string;
codigoSubgrupo?: string;
codigoConta?: string;
};
}
export default function AnaliticoComponent({ filtros }: AnaliticoProps) {
const [data, setData] = useState<AnaliticoItem[]>([]);
const [loading, setLoading] = useState(false);
const [sortConfig, setSortConfig] = useState<SortConfig>({
field: 'data_competencia',
direction: 'desc',
});
const fetchData = useCallback(async () => {
// Só faz a requisição se tiver dataInicio e dataFim
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]);
useEffect(() => {
fetchData();
}, [fetchData]);
const handleSort = (field: SortField) => {
setSortConfig((prev) => ({
field,
direction:
prev.field === field && prev.direction === 'asc' ? 'desc' : 'asc',
}));
};
const getSortIcon = (field: SortField) => {
if (sortConfig.field !== field) {
return <ArrowUpDown className="ml-1 h-3 w-3" />;
}
return sortConfig.direction === 'asc' ? (
<ArrowUp className="ml-1 h-3 w-3" />
) : (
<ArrowDown className="ml-1 h-3 w-3" />
);
};
const sortedData = [...data].sort((a, b) => {
const aValue = a[sortConfig.field];
const bValue = b[sortConfig.field];
if (typeof aValue === 'string' && typeof bValue === 'string') {
return sortConfig.direction === 'asc'
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue);
}
if (typeof aValue === 'number' && typeof bValue === 'number') {
return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue;
}
return 0;
});
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
}).format(value);
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('pt-BR');
};
const totalValor = data.reduce((sum, item) => {
const valor =
typeof item.valor === 'string' ? parseFloat(item.valor) : item.valor;
return sum + (isNaN(valor) ? 0 : valor);
}, 0);
return (
2025-10-08 11:57:42 +00:00
<div className="w-full mt-2 border-t pt-1">
<div className="flex justify-center items-center mb-1">
<h2 className="text-lg font-bold">Análise Analítica</h2>
</div>
{/* Filtros aplicados */}
{/* <div className="mb-4 p-3 bg-gray-50 rounded-md">
<div className="text-sm">
<strong>Filtros aplicados:</strong>
<div className="flex flex-wrap gap-2 mt-1">
{filtros.centroCusto && (
<span className="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs">
Centro: {filtros.centroCusto}
</span>
)}
{filtros.codigoGrupo && (
<span className="px-2 py-1 bg-green-100 text-green-800 rounded text-xs">
Grupo: {filtros.codigoGrupo}
</span>
)}
{filtros.codigoSubgrupo && (
<span className="px-2 py-1 bg-yellow-100 text-yellow-800 rounded text-xs">
Subgrupo: {filtros.codigoSubgrupo}
</span>
)}
{filtros.codigoConta && (
<span className="px-2 py-1 bg-purple-100 text-purple-800 rounded text-xs">
Conta: {filtros.codigoConta}
</span>
)}
</div>
</div>
</div> */}
{/* Resumo */}
{/* Tabela */}
2025-10-08 11:57:42 +00:00
<div className="w-[95%] max-h-[400px] overflow-y-auto border rounded-md relative mx-auto">
{/* Header fixo */}
<div
className="sticky top-0 z-30 border-b shadow-sm"
style={{ backgroundColor: 'white', opacity: 1 }}
>
<div
className="flex p-3 font-semibold text-xs"
style={{ backgroundColor: 'white', opacity: 1 }}
>
<div className="flex-1 min-w-[150px] max-w-[200px]">
<Button
variant="ghost"
onClick={() => handleSort('nome_fornecedor')}
className="h-auto p-0 font-semibold"
>
Fornecedor
{getSortIcon('nome_fornecedor')}
</Button>
</div>
<div className="flex-1 min-w-[100px] max-w-[120px] text-right">
<Button
variant="ghost"
onClick={() => handleSort('data_competencia')}
className="h-auto p-0 font-semibold"
>
Data
{getSortIcon('data_competencia')}
</Button>
</div>
<div className="flex-1 min-w-[120px] max-w-[150px] text-right">
<Button
variant="ghost"
onClick={() => handleSort('conta')}
className="h-auto p-0 font-semibold"
>
Conta
{getSortIcon('conta')}
</Button>
</div>
<div className="flex-1 min-w-[100px] max-w-[120px] text-right">
<Button
variant="ghost"
onClick={() => handleSort('valor')}
className="h-auto p-0 font-semibold"
>
Valor
{getSortIcon('valor')}
</Button>
</div>
<div className="flex-1 min-w-[200px] max-w-[300px]">Histórico</div>
</div>
</div>
<div className="flex flex-col">
{loading ? (
<div className="p-8 text-center text-sm text-gray-500">
Carregando dados analíticos...
</div>
) : sortedData.length === 0 ? (
<div className="p-8 text-center text-sm text-gray-500">
Nenhum dado analítico encontrado para os filtros aplicados.
</div>
) : (
sortedData.map((row, index) => (
<div key={index} className="flex border-b hover:bg-gray-50">
<div className="flex-1 min-w-[150px] max-w-[200px] p-1 text-xs">
{row.nome_fornecedor || '-'}
</div>
<div className="flex-1 min-w-[100px] max-w-[120px] text-right p-1 text-xs">
{formatDate(row.data_competencia)}
</div>
<div className="flex-1 min-w-[120px] max-w-[150px] text-right p-1 text-xs">
{row.conta}
</div>
<div className="flex-1 min-w-[100px] max-w-[120px] text-right p-1 text-xs font-medium">
{formatCurrency(
typeof row.valor === 'string'
? parseFloat(row.valor)
: row.valor
)}
</div>
<div className="flex-1 min-w-[200px] max-w-[300px] p-1 text-xs">
{row.historico || '-'}
</div>
</div>
))
)}
</div>
</div>
{data.length > 0 && (
2025-10-08 11:57:42 +00:00
<div className="w-[95%] mb-4 p-4 bg-blue-50 border rounded-md mx-auto">
<div className="flex justify-between items-center">
<div>
<h3 className="text-sm font-semibold">
Total de Registros: {data.length}
</h3>
</div>
<div>
<h3 className="text-sm font-semibold">
Valor Total: {formatCurrency(totalValor)}
</h3>
</div>
</div>
</div>
)}
</div>
);
}