improving list of assets
Some checks failed
Frontend Build and Deploy / build (push) Failing after 49s
Some checks failed
Frontend Build and Deploy / build (push) Failing after 49s
This commit is contained in:
parent
98b104b573
commit
0188dcd32b
@ -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: '^_',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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}`);
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user