aboutsummaryrefslogtreecommitdiff
path: root/src/components/ui/expenses-chart.tsx
diff options
context:
space:
mode:
authorLucas Faria Mendes <lucas.fariamo08@gmail.com>2026-08-14 18:06:35 +0000
committerLucas Faria Mendes <lucas.fariamo08@gmail.com>2026-08-14 18:06:35 +0000
commit2c2dfba45357bbd205a59145ae55a2779c911ae0 (patch)
tree0736b848aed56ec2da3c2489c49da0519447703d /src/components/ui/expenses-chart.tsx
downloadyaba-2c2dfba45357bbd205a59145ae55a2779c911ae0.tar.gz
yaba-2c2dfba45357bbd205a59145ae55a2779c911ae0.zip
remake
Signed-off-by: Lucas Faria Mendes <lucas.fariamo08@gmail.com>
Diffstat (limited to 'src/components/ui/expenses-chart.tsx')
-rw-r--r--src/components/ui/expenses-chart.tsx187
1 files changed, 187 insertions, 0 deletions
diff --git a/src/components/ui/expenses-chart.tsx b/src/components/ui/expenses-chart.tsx
new file mode 100644
index 0000000..efd5dee
--- /dev/null
+++ b/src/components/ui/expenses-chart.tsx
@@ -0,0 +1,187 @@
+import React from 'react';
+import { StyleSheet, Text, View } from 'react-native';
+import { PieChart } from 'react-native-gifted-charts';
+import { Colors, Radius, Spacing, Typography } from '@/constants/theme';
+import type { DashboardExpenseItem } from '@/types';
+import { Card, SectionLabel } from './primitives';
+
+interface ExpensesChartProps {
+ recurring: DashboardExpenseItem[];
+ oneTime: DashboardExpenseItem[];
+}
+
+const CATEGORY_COLORS: Record<string, string> = {
+ moradia: '#4C6EF5',
+ alimentação: '#FA5252',
+ transporte: '#FAB005',
+ saúde: '#12B886',
+ educação: '#BE4BDB',
+ lazer: '#FF922B',
+ serviços: '#228BE6',
+ outros: '#868E96',
+};
+
+const CATEGORY_LABELS: Record<string, string> = {
+ moradia: 'Moradia',
+ alimentação: 'Alimentação',
+ transporte: 'Transporte',
+ saúde: 'Saúde',
+ educação: 'Educação',
+ lazer: 'Lazer',
+ serviços: 'Serviços',
+ outros: 'Outros',
+};
+
+export function ExpensesChart({ recurring, oneTime }: ExpensesChartProps) {
+ const categoryTotals: Record<string, number> = {};
+
+ const allExpenses = [
+ ...recurring.filter(e => e.is_active && e.type === 'expense'),
+ ...oneTime.filter(e => e.type === 'expense')
+ ];
+
+ if (allExpenses.length === 0) {
+ return (
+ <Card style={styles.card}>
+ <SectionLabel text="Análise de Gastos" />
+ <View style={styles.emptyContainer}>
+ <Text style={styles.emptyText}>Não há despesas para analisar neste mês.</Text>
+ </View>
+ </Card>
+ );
+ }
+
+ let totalExpenses = 0;
+ allExpenses.forEach(e => {
+ if (!categoryTotals[e.category]) {
+ categoryTotals[e.category] = 0;
+ }
+ categoryTotals[e.category] += e.amount;
+ totalExpenses += e.amount;
+ });
+
+ const pieData = Object.keys(categoryTotals)
+ .map(category => ({
+ value: categoryTotals[category],
+ color: CATEGORY_COLORS[category] || CATEGORY_COLORS.outros,
+ label: CATEGORY_LABELS[category] || 'Outros',
+ percentage: ((categoryTotals[category] / totalExpenses) * 100).toFixed(0) + '%',
+ }))
+ .sort((a, b) => b.value - a.value);
+
+ return (
+ <Card style={styles.card}>
+ <SectionLabel text="Análise de Gastos" />
+
+ <View style={styles.chartRow}>
+ <View style={styles.chartWrapper}>
+ <PieChart
+ data={pieData}
+ donut
+ radius={70}
+ innerRadius={50}
+ innerCircleColor={Colors.surface}
+ centerLabelComponent={() => {
+ return (
+ <View style={styles.centerLabel}>
+ <Text style={styles.centerLabelValue}>
+ {totalExpenses.toLocaleString('pt-BR', {
+ style: 'currency',
+ currency: 'BRL',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ })}
+ </Text>
+ <Text style={styles.centerLabelText}>Total</Text>
+ </View>
+ );
+ }}
+ />
+ </View>
+
+ <View style={styles.legendContainer}>
+ {pieData.map((item, index) => (
+ <View key={index} style={styles.legendItem}>
+ <View style={[styles.legendDot, { backgroundColor: item.color }]} />
+ <View style={styles.legendTextWrapper}>
+ <Text style={styles.legendLabel} numberOfLines={1}>{item.label}</Text>
+ <Text style={styles.legendValue}>
+ {item.value.toLocaleString('pt-BR', {
+ style: 'currency',
+ currency: 'BRL',
+ })} ({item.percentage})
+ </Text>
+ </View>
+ </View>
+ ))}
+ </View>
+ </View>
+ </Card>
+ );
+}
+
+const styles = StyleSheet.create({
+ card: {
+ gap: Spacing.md,
+ },
+ emptyContainer: {
+ paddingVertical: Spacing.xl,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ emptyText: {
+ ...Typography.body,
+ color: Colors.textMuted,
+ },
+ chartRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ chartWrapper: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ centerLabel: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ centerLabelValue: {
+ ...Typography.body,
+ fontFamily: 'DMSans_700Bold',
+ color: Colors.textPrimary,
+ fontSize: 14,
+ },
+ centerLabelText: {
+ ...Typography.caption,
+ color: Colors.textSecondary,
+ fontSize: 10,
+ },
+ legendContainer: {
+ flex: 1,
+ marginLeft: Spacing.lg,
+ gap: Spacing.sm,
+ },
+ legendItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ legendDot: {
+ width: 10,
+ height: 10,
+ borderRadius: 5,
+ marginRight: Spacing.xs,
+ },
+ legendTextWrapper: {
+ flex: 1,
+ },
+ legendLabel: {
+ ...Typography.caption,
+ color: Colors.textSecondary,
+ },
+ legendValue: {
+ ...Typography.caption,
+ color: Colors.textPrimary,
+ fontFamily: 'DMSans_600SemiBold',
+ },
+});