diff options
| author | Lucas Faria Mendes <lucas.fariamo08@gmail.com> | 2026-08-14 18:06:35 +0000 |
|---|---|---|
| committer | Lucas Faria Mendes <lucas.fariamo08@gmail.com> | 2026-08-14 18:06:35 +0000 |
| commit | 2c2dfba45357bbd205a59145ae55a2779c911ae0 (patch) | |
| tree | 0736b848aed56ec2da3c2489c49da0519447703d /src/components/ui/expenses-chart.tsx | |
| download | yaba-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.tsx | 187 |
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', + }, +}); |