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',
|
'warn',
|
||||||
{ allowConstantExport: true },
|
{ 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 React, { useState, useMemo } from 'react';
|
||||||
import { CurrencyDollarIcon } from '@heroicons/react/24/outline';
|
import { CurrencyDollarIcon, ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline';
|
||||||
import { type Asset } from '../../api';
|
import { type Asset } from '../../api';
|
||||||
|
|
||||||
interface AssetsComponentProps {
|
interface AssetsComponentProps {
|
||||||
@ -8,6 +8,8 @@ interface AssetsComponentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AssetsComponent: React.FC<AssetsComponentProps> = ({ assets, isLoading }) => {
|
const AssetsComponent: React.FC<AssetsComponentProps> = ({ assets, isLoading }) => {
|
||||||
|
const [expandedYears, setExpandedYears] = useState<Set<number>>(new Set());
|
||||||
|
|
||||||
const formatCurrency = (value: number) => {
|
const formatCurrency = (value: number) => {
|
||||||
return new Intl.NumberFormat('pt-BR', {
|
return new Intl.NumberFormat('pt-BR', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
@ -15,6 +17,44 @@ const AssetsComponent: React.FC<AssetsComponentProps> = ({ assets, isLoading })
|
|||||||
}).format(value);
|
}).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 (
|
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="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">
|
<div className="flex items-center mb-6">
|
||||||
@ -28,37 +68,77 @@ const AssetsComponent: React.FC<AssetsComponentProps> = ({ assets, isLoading })
|
|||||||
</div>
|
</div>
|
||||||
) : assets && assets.length > 0 ? (
|
) : assets && assets.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<div className="overflow-x-auto">
|
<div className="space-y-4">
|
||||||
<table className="w-full table-auto">
|
{sortedYears.map((year) => {
|
||||||
<thead>
|
const yearAssets = groupedAssets[year];
|
||||||
<tr className="border-b border-gray-200">
|
const isExpanded = expandedYears.has(year);
|
||||||
<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>
|
return (
|
||||||
<th className="text-left py-3 px-2 font-semibold text-gray-700">Descrição</th>
|
<div key={year} className="border border-gray-200 rounded-lg overflow-hidden">
|
||||||
<th className="text-right py-3 px-2 font-semibold text-gray-700">Valor</th>
|
{/* Year Header - Clickable */}
|
||||||
</tr>
|
<button
|
||||||
</thead>
|
onClick={() => toggleYear(year)}
|
||||||
<tbody>
|
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"
|
||||||
{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">
|
<div className="flex items-center space-x-3">
|
||||||
<td className="py-3 px-2 text-gray-900">{asset.ano}</td>
|
<span className="text-lg font-semibold text-gray-900">{year}</span>
|
||||||
<td className="py-3 px-2 text-gray-900">{asset.tipoBem}</td>
|
<span className="text-sm text-gray-600">
|
||||||
<td className="py-3 px-2 text-gray-900">{asset.descricao}</td>
|
({yearAssets.length} {yearAssets.length === 1 ? 'item' : 'itens'})
|
||||||
<td className="py-3 px-2 text-right text-gray-900 font-medium">
|
</span>
|
||||||
{formatCurrency(asset.valor)}
|
</div>
|
||||||
</td>
|
<div className="flex items-center space-x-3">
|
||||||
</tr>
|
<span className="text-lg font-bold text-purple-600">
|
||||||
))}
|
{formatCurrency(getTotalForYear(yearAssets))}
|
||||||
</tbody>
|
</span>
|
||||||
</table>
|
{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>
|
</div>
|
||||||
|
|
||||||
{/* Total Assets */}
|
{/* 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">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-lg font-semibold text-gray-700">Total Declarado:</span>
|
<span className="text-xl font-semibold text-gray-700">Total Geral Declarado:</span>
|
||||||
<span className="text-xl font-bold text-purple-600">
|
<span className="text-2xl font-bold text-purple-600">
|
||||||
{formatCurrency(assets.reduce((total, asset) => total + asset.valor, 0))}
|
{formatCurrency(getTotalAssets())}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -123,14 +123,12 @@ const MatrixBackground: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Check which dots are near the mouse
|
// Check which dots are near the mouse
|
||||||
let hoveredCount = 0;
|
|
||||||
dots.forEach((dot, index) => {
|
dots.forEach((dot, index) => {
|
||||||
const dx = dot.x - mouseX;
|
const dx = dot.x - mouseX;
|
||||||
const dy = dot.y - mouseY;
|
const dy = dot.y - mouseY;
|
||||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
if (distance < config.hoverRadius) {
|
if (distance < config.hoverRadius) {
|
||||||
hoveredCount++;
|
|
||||||
const intensity = 1 - (distance / config.hoverRadius);
|
const intensity = 1 - (distance / config.hoverRadius);
|
||||||
dot.targetBrightness = config.baseBrightness +
|
dot.targetBrightness = config.baseBrightness +
|
||||||
(config.hoverBrightness - config.baseBrightness) * intensity;
|
(config.hoverBrightness - config.baseBrightness) * intensity;
|
||||||
|
@ -31,7 +31,7 @@ const SearchBar: React.FC<SearchBarProps> = ({ className = '' }) => {
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let result = await openCandApi.searchCandidates(query.trim());
|
const result = await openCandApi.searchCandidates(query.trim());
|
||||||
setSearchResults(result.candidatos); // Limit to 8 results
|
setSearchResults(result.candidatos); // Limit to 8 results
|
||||||
setShowResults(true);
|
setShowResults(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -93,7 +93,7 @@ const SearchBar: React.FC<SearchBarProps> = ({ className = '' }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getCandidateDescription = (candidate: Candidate) => {
|
const getCandidateDescription = (candidate: Candidate) => {
|
||||||
let desc = [''];
|
const desc = [''];
|
||||||
|
|
||||||
if (candidate.cpf) desc.push(`CPF: ${maskCpf(candidate.cpf)}`);
|
if (candidate.cpf) desc.push(`CPF: ${maskCpf(candidate.cpf)}`);
|
||||||
if (candidate.ocupacao && candidate.ocupacao != 'OUTROS') desc.push(`${candidate.ocupacao}`);
|
if (candidate.ocupacao && candidate.ocupacao != 'OUTROS') desc.push(`${candidate.ocupacao}`);
|
||||||
|
@ -89,3 +89,19 @@ html, body, #root {
|
|||||||
#root {
|
#root {
|
||||||
min-height: 100vh;
|
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