Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
"@tanstack/react-query": "^5.90.2",
"@tanstack/react-query-persist-client": "^5.90.7",
"axios": "^1.12.2",
"big.js": "^5.2.1",
"jwt-decode": "^4.0.0",
"react": "^19.1.0",
"react-color": "^2.19.3",
"react-currency-input-field": "^3.10.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"react-intl-currency-input": "^0.2.6",
"react-router": "^7.6.3",
"react-toastify": "^11.0.5",
"recharts": "^3.1.0",
Expand Down
109 changes: 48 additions & 61 deletions frontend/src/components/ChartSwitcher/ExpenseChart.jsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,54 @@
import {
PieChart,
Pie,
Cell,
Tooltip,
Legend,
ResponsiveContainer,
} from 'recharts';
import userData from '../../mockData/user/user.data';
import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from 'recharts';
import { avatarBackgroundColors } from '../../mockData/colorsPallete/colors';
import { useMeDashboardQuery } from '../../hooks/ReactQuery/useMeDashboardStatsQuery';

const COLORS = [
'#ff69b4', // Rosa vibrante
'#ffb6c1', // Rosa claro
'#ffd700', // Dourado
'#ba55d3', // Roxo médio
'#87cefa', // Azul claro
'#ffa07a', // Coral suave
];

export default function ExpensePieChart() {
const expenseData = userData.dashboard.chartsData.expenseData;
export default function ExpenseQuantityDonutChart() {
const { data: expenseData } = useMeDashboardQuery();
const total = expenseData?.reduce((sum, item) => sum + item.quantity, 0) || 0;

return (
<ResponsiveContainer width="100%" height="100%">
<PieChart>
{/* Pie principal */}
<Pie
data={expenseData}
dataKey="valor"
nameKey="categoria"
cx="50%"
cy="50%"
outerRadius="80%"
label={{ fill: 'var(--color-text)', fontWeight: 500 }}
>
{expenseData.map((entry, index) => (
<Cell
key={`${entry}-cell-${index}`}
fill={COLORS[index % COLORS.length]}
/>
))}
</Pie>

{/* Tooltip */}
<Tooltip
contentStyle={{
backgroundColor: 'var(--color-surface)',
borderColor: 'var(--color-muted)',
color: 'var(--color-text)',
}}
labelStyle={{ color: 'var(--color-text)', fontWeight: 500 }}
itemStyle={{ color: 'var(--color-text)' }}
/>
<div className="relative w-full h-full">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={expenseData}
dataKey="quantity"
nameKey="category"
innerRadius="60%"
outerRadius="80%"
label={({ name, percent }) =>

Check failure on line 19 in frontend/src/components/ChartSwitcher/ExpenseChart.jsx

View workflow job for this annotation

GitHub Actions / Frontend (React)

'percent' is defined but never used
`${name}: ${expenseData.find(c => c.category === name)?.quantity}`
}
labelLine={true}
>
{expenseData?.map((_, index) => (
<Cell
key={index}
fill={
avatarBackgroundColors[index % avatarBackgroundColors.length]
}
/>
))}
</Pie>
<Tooltip
contentStyle={{
backgroundColor: 'var(--color-surface)',
borderColor: 'var(--color-muted)',
color: 'var(--color-text)',
borderRadius: '8px',
}}
labelStyle={{ color: 'var(--color-text)', fontWeight: 600 }}
formatter={value => [`${value}`, 'Quantidade de despesas']}
/>
</PieChart>
</ResponsiveContainer>

{/* Legenda */}
<Legend
layout="horizontal"
verticalAlign="bottom"
wrapperStyle={{
color: 'var(--color-text)',
fontWeight: 500,
}}
/>
</PieChart>
</ResponsiveContainer>
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center">
<p className="text-sm text-muted">Total</p>
<p className="text-lg font-semibold text-text">{total}</p>
</div>
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion frontend/src/components/ChartSwitcher/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default function ChartSwitcher() {
</div>

{/* Área do gráfico */}
<div className="flex items-center justify-center w-full h-72">
<div className=" w-full h-72">
{activeChart === 'balance' ? <BalanceChart /> : <ExpenseChart />}
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/Filters/Groups/useFilteredGroups.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { useMemo } from 'react';

export function useFilteredGroups(groups, currentUserId, filters) {
export function useFilteredGroups(groups, role, filters) {
const { search, onlyOwner, sortOrder } = filters;

return useMemo(() => {
return groups
?.filter(
group =>
(!onlyOwner || group.ownerId === currentUserId) &&
(!onlyOwner || group.authority === role) &&
group.name.toLowerCase().includes(search.trim().toLowerCase())
)
.sort((a, b) => {
const aDate = new Date(a.createdAt).getTime();
const bDate = new Date(b.createdAt).getTime();
return sortOrder === 'asc' ? aDate - bDate : bDate - aDate;
});
}, [groups, currentUserId, filters]);
}, [groups, role, filters]);

Check warning on line 18 in frontend/src/components/Filters/Groups/useFilteredGroups.js

View workflow job for this annotation

GitHub Actions / Frontend (React)

React Hook useMemo has missing dependencies: 'onlyOwner', 'search', and 'sortOrder'. Either include them or remove the dependency array
}
8 changes: 2 additions & 6 deletions frontend/src/components/LatestExpenses/index.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { Menu, MenuItem } from '@headlessui/react';
import userData from '../../mockData/user/user.data';
import {
formatBRL,
formatDateBR,
formatRelativeDate,
} from '../../utils/formatters';
import useMeQuery from '../../hooks/ReactQuery/useMeQuery';
import useMeExpensesQuery from '../../hooks/ReactQuery/useMeExpensesQuery';

export default function LatestExpenses() {
const current_user = userData;
const { data: me } = useMeQuery();
const myExpenses = me?.totalExpenses;
console.log(myExpenses);
const { data: myExpenses } = useMeExpensesQuery();
return (
<section
aria-labelledby="ultimas-despesas-heading"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import ButtonUI from '../../components/ui/Button';
import ButtonUI from '../ui/Button';

export default function Pagination({ page, totalPages, onNext, onPrev }) {
return (
<div className="flex justify-center gap-4 mt-6">
<div className="flex w-full justify-center gap-4 mt-6">
<ButtonUI
onClick={onPrev}
disabled={page === 0}
className="px-4 py-2 bg-primary text-white rounded disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed"
className="px-4 py-2 bg-primary text-white rounded disabled:opacity-50 disabled:bg-gray-400 cursor-pointer disabled:cursor-not-allowed"
>
{'<'}
</ButtonUI>
Expand All @@ -18,7 +18,7 @@ export default function Pagination({ page, totalPages, onNext, onPrev }) {
<ButtonUI
onClick={onNext}
disabled={page === totalPages - 1}
className="px-4 py-2 bg-primary text-white rounded disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed"
className="px-4 py-2 bg-primary text-white rounded disabled:opacity-50 disabled:bg-gray-400 cursor-pointer disabled:cursor-not-allowed"
>
{'>'}
</ButtonUI>
Expand Down
159 changes: 122 additions & 37 deletions frontend/src/components/forms/ExpenseForm/CustomSplitAmount/index.jsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,139 @@
import { useEffect, useState } from 'react';
import { useFormExpense } from '../useForm';
import CurrencyInputUI from '../../../ui/CurrencyInput';
import { formatBRL } from '../../../../utils/formatters';
import { FiCheck } from 'react-icons/fi';

export default function CustomSplitAmount({ members }) {
const {
amount,
divisionAmount,
updateMemberShare,
remainingDifference,
redistributeEvenly,
getRemainingDifference,
isBalanced,
} = useFormExpense();

// Divide igualmente apenas na primeira renderização
useEffect(() => {
if (members?.length && amount > 0) {
const hasAnyValue = Object.values(divisionAmount).some(v => v?.float > 0);
if (!hasAnyValue) redistributeEvenly();
}
}, []); // executa apenas 1x

Check warning on line 22 in frontend/src/components/forms/ExpenseForm/CustomSplitAmount/index.jsx

View workflow job for this annotation

GitHub Actions / Frontend (React)

React Hook useEffect has missing dependencies: 'amount', 'divisionAmount', 'members?.length', and 'redistributeEvenly'. Either include them or remove the dependency array

const remainingDifference = getRemainingDifference();
const balanced = isBalanced();

return (
<div className="col-span-2 mt-4">
<p className="font-semibold">Valores por pessoa</p>
<div className="flex flex-col gap-2 mt-2">
{members.map(({ id, name }) => (
<div key={id || name} className="flex items-center gap-2">
<span className="w-1/2">{name}</span>
<CurrencyInputUI
name={name}
value={divisionAmount[name]?.value ?? ''}
onValueChange={(_, __, values) => {
updateMemberShare(name, values);
}}
className="w-full p-2 border rounded"
<div className="col-span-2 mt-6">
<h3 className="text-lg font-semibold text-text mb-3">
💰 Distribuição personalizada
</h3>

<div className="flex flex-col gap-3">
{members.map(({ id, name }) => {
const member = divisionAmount[id] || { float: 0, formatted: '' };
return (
<CustomMemberInput
key={id}
balanced={balanced}
member={{ id, member, name }}
/>
</div>
))}
);
})}
</div>

{/* Status de equilíbrio */}
{amount > 0 && (
<div
className={`mt-6 rounded-xl border p-3 text-sm font-medium text-center transition ${
balanced
? 'bg-green-50 border-green-200 text-green-700'
: 'bg-yellow-50 border-yellow-200 text-yellow-700'
}`}
>
{balanced ? (
<span className="flex items-center justify-center gap-1">
<span>✅</span> Distribuição correta
</span>
) : (
<div className="flex flex-col items-center gap-2">
<p>
⚠️ Diferença restante:{' '}
<span className="font-semibold">
{formatBRL(remainingDifference)}
</span>
</p>
<button
type="button"
onClick={redistributeEvenly}
className="text-sm px-3 py-1.5 rounded-md bg-primary/80 hover:bg-primary text-white transition"
>
Redistribuir igualmente
</button>
</div>
)}
</div>
)}
</div>
);
}

function CustomMemberInput({ balanced, member: { id, member, name } }) {
const { updateMemberShare } = useFormExpense();
const [localValue, setLocalValue] = useState('');
const [hasChanged, setHasChanged] = useState(false);

const valueColor =
member.float === 0
? 'text-muted'
: balanced
? 'text-success'
: 'text-error';

// função chamada ao confirmar manualmente
const handleConfirm = () => {
updateMemberShare(id, localValue);
setHasChanged(false);
};

return (
<div
key={id}
className="flex flex-col gap-2 bg-surface border border-border rounded-xl p-3 shadow-sm hover:shadow transition-all duration-200"
>
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-text/90">{name}</span>
<span
className={`text-sm font-semibold transition-colors duration-200 ${valueColor}`}
>
Participação: {member.float ? formatBRL(member.float) : 'R$ 0,00'}
</span>
</div>

<div
className={`mt-4 p-2 rounded ${
Math.abs(remainingDifference) < 0.01
? 'bg-green-100 text-green-700'
: 'bg-yellow-100 text-yellow-700'
}`}
>
{Math.abs(remainingDifference) < 0.01 ? (
'✓ Distribuição correta'
) : (
<>
<p>⚠️ Diferença: {formatBRL(remainingDifference)}</p>
<button
type="button"
onClick={redistributeEvenly}
className="mt-2 text-sm text-white bg-primary/70 hover:bg-primary transition rounded-md p-2"
>
Redistribuir igualmente
</button>
</>
<div className="flex items-center gap-2">
<CurrencyInputUI
name={String(id)}
/* value={localValue} */
placeholder="Digite o valor"
onValueChange={(_, __, values) => {
setLocalValue(values?.float ?? '');
setHasChanged(true);
}}
className="flex-1 p-2 border border-border rounded-md bg-background focus:ring-2 focus:ring-primary/40 transition outline-none"
/>

{/* Botão de confirmação aparece só se houver mudança */}
{hasChanged && localValue !== '' && (
<button
type="button"
onClick={handleConfirm}
title="Confirmar valor?"
className="p-2 bg-primary text-white rounded-md hover:bg-primary/90 transition flex items-center justify-center cursor-pointer"
aria-label="Confirmar valor"
>
<FiCheck size={18} />
</button>
)}
</div>
</div>
Expand Down
Loading
Loading