improving list of assets
Some checks failed
Frontend Build and Deploy / build (push) Failing after 49s

This commit is contained in:
José Henrique 2025-06-03 16:26:48 -03:00
parent 98b104b573
commit 0188dcd32b
5 changed files with 138 additions and 33 deletions

View File

@ -23,6 +23,17 @@ export default tseslint.config(
'warn',
{ allowConstantExport: true },
],
// no used vars ignored
'@typescript-eslint/no-unused-vars': [
'warn',
{
vars: 'all',
args: 'after-used',
ignoreRestSiblings: true,
varsIgnorePattern: '^_',
argsIgnorePattern: '^_',
},
],
},
},
)

View File

@ -1,5 +1,5 @@
import React from 'react';
import { CurrencyDollarIcon } from '@heroicons/react/24/outline';
import React, { useState, useMemo } from 'react';
import { CurrencyDollarIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline';
import { type Asset } from '../../api';
interface AssetsComponentProps {
@ -8,6 +8,8 @@ interface AssetsComponentProps {
}
const AssetsComponent: React.FC<AssetsComponentProps> = ({ assets, isLoading }) => {
const [expandedYears, setExpandedYears] = useState<Set<number>>(new Set());
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
@ -15,6 +17,44 @@ const AssetsComponent: React.FC<AssetsComponentProps> = ({ assets, isLoading })
}).format(value);
};
const groupedAssets = useMemo(() => {
if (!assets) return {};
return assets.reduce((acc, asset) => {
const year = asset.ano;
if (!acc[year]) {
acc[year] = [];
}
acc[year].push(asset);
return acc;
}, {} as Record<number, Asset[]>);
}, [assets]);
const sortedYears = useMemo(() => {
return Object.keys(groupedAssets).map(Number).sort((a, b) => b - a);
}, [groupedAssets]);
const toggleYear = (year: number) => {
setExpandedYears(prev => {
const newSet = new Set(prev);
if (newSet.has(year)) {
newSet.delete(year);
} else {
newSet.add(year);
}
return newSet;
});
};
const getTotalForYear = (yearAssets: Asset[]) => {
return yearAssets.reduce((total, asset) => total + asset.valor, 0);
};
const getTotalAssets = () => {
if (!assets) return 0;
return assets.reduce((total, asset) => total + asset.valor, 0);
};
return (
<div className="bg-white/95 backdrop-blur-sm rounded-xl shadow-lg hover:shadow-xl transform hover:scale-[1.02] transition-all duration-200 p-6">
<div className="flex items-center mb-6">
@ -28,37 +68,77 @@ const AssetsComponent: React.FC<AssetsComponentProps> = ({ assets, isLoading })
</div>
) : assets && assets.length > 0 ? (
<>
<div className="overflow-x-auto">
<table className="w-full table-auto">
<thead>
<tr className="border-b border-gray-200">
<th className="text-left py-3 px-2 font-semibold text-gray-700">Ano</th>
<th className="text-left py-3 px-2 font-semibold text-gray-700">Tipo</th>
<th className="text-left py-3 px-2 font-semibold text-gray-700">Descrição</th>
<th className="text-right py-3 px-2 font-semibold text-gray-700">Valor</th>
</tr>
</thead>
<tbody>
{assets.map((asset: Asset, index: number) => (
<tr key={`${asset.idCandidato}-${asset.ano}-${index}`} className="border-b border-gray-100 hover:bg-gray-50 transition-colors">
<td className="py-3 px-2 text-gray-900">{asset.ano}</td>
<td className="py-3 px-2 text-gray-900">{asset.tipoBem}</td>
<td className="py-3 px-2 text-gray-900">{asset.descricao}</td>
<td className="py-3 px-2 text-right text-gray-900 font-medium">
{formatCurrency(asset.valor)}
</td>
</tr>
))}
</tbody>
</table>
<div className="space-y-4">
{sortedYears.map((year) => {
const yearAssets = groupedAssets[year];
const isExpanded = expandedYears.has(year);
return (
<div key={year} className="border border-gray-200 rounded-lg overflow-hidden">
{/* Year Header - Clickable */}
<button
onClick={() => toggleYear(year)}
className="w-full px-4 py-4 bg-gray-50 hover:bg-gray-100 transition-colors duration-200 flex items-center justify-between focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-inset"
>
<div className="flex items-center space-x-3">
<span className="text-lg font-semibold text-gray-900">{year}</span>
<span className="text-sm text-gray-600">
({yearAssets.length} {yearAssets.length === 1 ? 'item' : 'itens'})
</span>
</div>
<div className="flex items-center space-x-3">
<span className="text-lg font-bold text-purple-600">
{formatCurrency(getTotalForYear(yearAssets))}
</span>
{isExpanded ? (
<ChevronUpIcon className="h-5 w-5 text-gray-500 transition-transform duration-200" />
) : (
<ChevronDownIcon className="h-5 w-5 text-gray-500 transition-transform duration-200" />
)}
</div>
</button>
{/* Assets Table - Collapsible */}
{isExpanded && (
<div className="bg-white animate-fadeIn">
<div className="overflow-x-auto">
<table className="w-full table-auto">
<thead>
<tr className="border-b border-gray-200 bg-gray-50">
<th className="text-left py-3 px-4 font-semibold text-gray-700">Tipo</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700">Descrição</th>
<th className="text-right py-3 px-4 font-semibold text-gray-700">Valor</th>
</tr>
</thead>
<tbody>
{yearAssets.map((asset: Asset, index: number) => (
<tr
key={`${asset.idCandidato}-${asset.ano}-${index}`}
className="border-b border-gray-100 hover:bg-gray-50 transition-colors duration-150"
>
<td className="py-3 px-4 text-gray-900">{asset.tipoBem}</td>
<td className="py-3 px-4 text-gray-900">{asset.descricao}</td>
<td className="py-3 px-4 text-right text-gray-900 font-medium">
{formatCurrency(asset.valor)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
);
})}
</div>
{/* Total Assets */}
<div className="mt-4 pt-4 border-t border-gray-200">
<div className="mt-6 pt-4 border-t-2 border-gray-200">
<div className="flex justify-between items-center">
<span className="text-lg font-semibold text-gray-700">Total Declarado:</span>
<span className="text-xl font-bold text-purple-600">
{formatCurrency(assets.reduce((total, asset) => total + asset.valor, 0))}
<span className="text-xl font-semibold text-gray-700">Total Geral Declarado:</span>
<span className="text-2xl font-bold text-purple-600">
{formatCurrency(getTotalAssets())}
</span>
</div>
</div>

View File

@ -123,14 +123,12 @@ const MatrixBackground: React.FC = () => {
});
// Check which dots are near the mouse
let hoveredCount = 0;
dots.forEach((dot, index) => {
const dx = dot.x - mouseX;
const dy = dot.y - mouseY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < config.hoverRadius) {
hoveredCount++;
const intensity = 1 - (distance / config.hoverRadius);
dot.targetBrightness = config.baseBrightness +
(config.hoverBrightness - config.baseBrightness) * intensity;

View File

@ -31,7 +31,7 @@ const SearchBar: React.FC<SearchBarProps> = ({ className = '' }) => {
setError(null);
try {
let result = await openCandApi.searchCandidates(query.trim());
const result = await openCandApi.searchCandidates(query.trim());
setSearchResults(result.candidatos); // Limit to 8 results
setShowResults(true);
} catch (err) {
@ -93,7 +93,7 @@ const SearchBar: React.FC<SearchBarProps> = ({ className = '' }) => {
}, []);
const getCandidateDescription = (candidate: Candidate) => {
let desc = [''];
const desc = [''];
if (candidate.cpf) desc.push(`CPF: ${maskCpf(candidate.cpf)}`);
if (candidate.ocupacao && candidate.ocupacao != 'OUTROS') desc.push(`${candidate.ocupacao}`);

View File

@ -89,3 +89,19 @@ html, body, #root {
#root {
min-height: 100vh;
}
/* Custom fade-in animation for dropdown content */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fadeIn {
animation: fadeIn 0.3s ease-out forwards;
}