diff --git a/package-lock.json b/package-lock.json index c4ed937..6ee2827 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.4", + "@react-three/fiber": "^8.18.0", "@supabase/supabase-js": "^2.49.4", "@tabler/icons-react": "^3.34.0", "@tanstack/react-query": "^5.56.2", @@ -58,6 +59,7 @@ "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", + "three": "^0.177.0", "vaul": "^0.9.3", "zod": "^3.23.8", "zustand": "^4.4.7" @@ -2324,6 +2326,81 @@ "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", "license": "MIT" }, + "node_modules/@react-three/fiber": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.18.0.tgz", + "integrity": "sha512-FYZZqD0UUHUswKz3LQl2Z7H24AhD14XGTsIRw3SJaXUxyfVMi+1yiZGmqTcPt/CkPpdU7rrxqcyQ1zJE5DjvIQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/react-reconciler": "^0.26.7", + "@types/webxr": "*", + "base64-js": "^1.5.1", + "buffer": "^6.0.3", + "its-fine": "^1.0.6", + "react-reconciler": "^0.27.0", + "react-use-measure": "^2.1.7", + "scheduler": "^0.21.0", + "suspend-react": "^0.1.3", + "zustand": "^3.7.1" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-file-system": ">=11.0", + "expo-gl": ">=11.0", + "react": ">=18 <19", + "react-dom": ">=18 <19", + "react-native": ">=0.64", + "three": ">=0.133" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-file-system": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber/node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/@react-three/fiber/node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "license": "MIT", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", @@ -3033,14 +3110,12 @@ "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -3057,6 +3132,21 @@ "@types/react": "*" } }, + "node_modules/@types/react-reconciler": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", + "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.22", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.22.tgz", + "integrity": "sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -3469,6 +3559,26 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3539,6 +3649,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4929,6 +5063,26 @@ "node": ">= 0.4" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5073,6 +5227,27 @@ "dev": true, "license": "ISC" }, + "node_modules/its-fine": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz", + "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==", + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.28.0" + }, + "peerDependencies": { + "react": ">=18.0" + } + }, + "node_modules/its-fine/node_modules/@types/react-reconciler": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", + "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -6297,6 +6472,31 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/react-reconciler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz", + "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.21.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/react-reconciler/node_modules/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/react-remove-scroll": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", @@ -6440,6 +6640,21 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-use-measure": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", + "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -6844,6 +7059,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, "node_modules/tailwind-merge": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", @@ -6931,6 +7155,12 @@ "node": ">=0.8" } }, + "node_modules/three": { + "version": "0.177.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.177.0.tgz", + "integrity": "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==", + "license": "MIT" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", diff --git a/package.json b/package.json index fafa829..19a7104 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-tooltip": "^1.1.4", + "@react-three/fiber": "^8.18.0", "@supabase/supabase-js": "^2.49.4", "@tabler/icons-react": "^3.34.0", "@tanstack/react-query": "^5.56.2", @@ -61,6 +62,7 @@ "sonner": "^1.5.0", "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", + "three": "^0.177.0", "vaul": "^0.9.3", "zod": "^3.23.8", "zustand": "^4.4.7" diff --git a/src/components/dashboard/CategoryChart.tsx b/src/components/dashboard/CategoryChart.tsx index 3e5af21..fad4aa4 100644 --- a/src/components/dashboard/CategoryChart.tsx +++ b/src/components/dashboard/CategoryChart.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { CardSpotlight } from '@/components/ui/card-spotlight'; import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; import { CategorySummary } from '@/types/financialTypes'; @@ -52,13 +52,13 @@ const CategoryChart: React.FC = ({ categories, isLoading = f }; return ( - - - + +
+
🧾 Gastos por Categoria - - - +
+
+
{isLoading ? (
@@ -101,8 +101,8 @@ const CategoryChart: React.FC = ({ categories, isLoading = f
)} - - +
+ ); }; diff --git a/src/components/transacoes/TransactionSummaryCards.tsx b/src/components/transacoes/TransactionSummaryCards.tsx index aabbd4d..86e66b9 100644 --- a/src/components/transacoes/TransactionSummaryCards.tsx +++ b/src/components/transacoes/TransactionSummaryCards.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { CardSpotlight } from '@/components/ui/card-spotlight'; import { ArrowUp, ArrowDown, CreditCard } from 'lucide-react'; interface TransactionSummaryCardsProps { @@ -21,42 +21,42 @@ export const TransactionSummaryCards = ({ return (
- - - + +
+
Ganhos do mês - - - +
+
+

{formatCurrency(totalReceitas)}

- - +
+
- - - + +
+
Gastos do mês - - - +
+
+

{formatCurrency(totalDespesas)}

- - +
+
- - - + +
+
Gastos em cartões - - - +
+
+

{formatCurrency(totalCartoes)}

@@ -64,8 +64,8 @@ export const TransactionSummaryCards = ({ Total geral: {formatCurrency(totalGeral)}
- -
+
+
); }; diff --git a/src/components/ui/canvas-reveal-effect.tsx b/src/components/ui/canvas-reveal-effect.tsx new file mode 100644 index 0000000..b138da0 --- /dev/null +++ b/src/components/ui/canvas-reveal-effect.tsx @@ -0,0 +1,309 @@ + +"use client"; +import { cn } from "@/lib/utils"; +import { Canvas, useFrame, useThree } from "@react-three/fiber"; +import React, { useMemo, useRef } from "react"; +import * as THREE from "three"; + +export const CanvasRevealEffect = ({ + animationSpeed = 0.4, + opacities = [0.3, 0.3, 0.3, 0.5, 0.5, 0.5, 0.8, 0.8, 0.8, 1], + colors = [[0, 255, 255]], + containerClassName, + dotSize, + showGradient = true, +}: { + /** + * 0.1 - slower + * 1.0 - faster + */ + animationSpeed?: number; + opacities?: number[]; + colors?: number[][]; + containerClassName?: string; + dotSize?: number; + showGradient?: boolean; +}) => { + return ( +
+
+ +
+ {showGradient && ( +
+ )} +
+ ); +}; + +interface DotMatrixProps { + colors?: number[][]; + opacities?: number[]; + totalSize?: number; + dotSize?: number; + shader?: string; + center?: ("x" | "y")[]; +} + +const DotMatrix: React.FC = ({ + colors = [[0, 0, 0]], + opacities = [0.04, 0.04, 0.04, 0.04, 0.04, 0.08, 0.08, 0.08, 0.08, 0.14], + totalSize = 4, + dotSize = 2, + shader = "", + center = ["x", "y"], +}) => { + const uniforms = React.useMemo(() => { + let colorsArray = [ + colors[0], + colors[0], + colors[0], + colors[0], + colors[0], + colors[0], + ]; + if (colors.length === 2) { + colorsArray = [ + colors[0], + colors[0], + colors[0], + colors[1], + colors[1], + colors[1], + ]; + } else if (colors.length === 3) { + colorsArray = [ + colors[0], + colors[0], + colors[1], + colors[1], + colors[2], + colors[2], + ]; + } + + return { + u_colors: { + value: colorsArray.map((color) => [ + color[0] / 255, + color[1] / 255, + color[2] / 255, + ]), + type: "uniform3fv", + }, + u_opacities: { + value: opacities, + type: "uniform1fv", + }, + u_total_size: { + value: totalSize, + type: "uniform1f", + }, + u_dot_size: { + value: dotSize, + type: "uniform1f", + }, + }; + }, [colors, opacities, totalSize, dotSize]); + + return ( + + ); +}; + +type Uniforms = { + [key: string]: { + value: number[] | number[][] | number; + type: string; + }; +}; +const ShaderMaterial = ({ + source, + uniforms, + maxFps = 60, +}: { + source: string; + hovered?: boolean; + maxFps?: number; + uniforms: Uniforms; +}) => { + const { size } = useThree(); + const ref = useRef(); + let lastFrameTime = 0; + + useFrame(({ clock }) => { + if (!ref.current) return; + const timestamp = clock.getElapsedTime(); + if (timestamp - lastFrameTime < 1 / maxFps) { + return; + } + lastFrameTime = timestamp; + + const material: any = ref.current.material; + const timeLocation = material.uniforms.u_time; + timeLocation.value = timestamp; + }); + + const getUniforms = () => { + const preparedUniforms: any = {}; + + for (const uniformName in uniforms) { + const uniform: any = uniforms[uniformName]; + + switch (uniform.type) { + case "uniform1f": + preparedUniforms[uniformName] = { value: uniform.value, type: "1f" }; + break; + case "uniform3f": + preparedUniforms[uniformName] = { + value: new THREE.Vector3().fromArray(uniform.value), + type: "3f", + }; + break; + case "uniform1fv": + preparedUniforms[uniformName] = { value: uniform.value, type: "1fv" }; + break; + case "uniform3fv": + preparedUniforms[uniformName] = { + value: uniform.value.map((v: number[]) => + new THREE.Vector3().fromArray(v) + ), + type: "3fv", + }; + break; + case "uniform2f": + preparedUniforms[uniformName] = { + value: new THREE.Vector2().fromArray(uniform.value), + type: "2f", + }; + break; + default: + console.error(`Invalid uniform type for '${uniformName}'.`); + break; + } + } + + preparedUniforms["u_time"] = { value: 0, type: "1f" }; + preparedUniforms["u_resolution"] = { + value: new THREE.Vector2(size.width * 2, size.height * 2), + }; + return preparedUniforms; + }; + + const material = useMemo(() => { + const materialObject = new THREE.ShaderMaterial({ + vertexShader: ` + precision mediump float; + in vec2 coordinates; + uniform vec2 u_resolution; + out vec2 fragCoord; + void main(){ + float x = position.x; + float y = position.y; + gl_Position = vec4(x, y, 0.0, 1.0); + fragCoord = (position.xy + vec2(1.0)) * 0.5 * u_resolution; + fragCoord.y = u_resolution.y - fragCoord.y; + } + `, + fragmentShader: source, + uniforms: getUniforms(), + glslVersion: THREE.GLSL3, + blending: THREE.CustomBlending, + blendSrc: THREE.SrcAlphaFactor, + blendDst: THREE.OneFactor, + }); + + return materialObject; + }, [size.width, size.height, source]); + + return ( + + + + + ); +}; + +const Shader: React.FC = ({ source, uniforms, maxFps = 60 }) => { + return ( + + + + ); +}; + +interface ShaderProps { + source: string; + uniforms: { + [key: string]: { + value: number[] | number[][] | number; + type: string; + }; + }; + maxFps?: number; +} diff --git a/src/components/ui/card-spotlight.tsx b/src/components/ui/card-spotlight.tsx new file mode 100644 index 0000000..becd361 --- /dev/null +++ b/src/components/ui/card-spotlight.tsx @@ -0,0 +1,75 @@ + +"use client"; + +import { useMotionValue, motion, useMotionTemplate } from "framer-motion"; +import React, { MouseEvent as ReactMouseEvent, useState } from "react"; +import { CanvasRevealEffect } from "@/components/ui/canvas-reveal-effect"; +import { cn } from "@/lib/utils"; + +export const CardSpotlight = ({ + children, + radius = 350, + color = "#262626", + className, + ...props +}: { + radius?: number; + color?: string; + children: React.ReactNode; +} & React.HTMLAttributes) => { + const mouseX = useMotionValue(0); + const mouseY = useMotionValue(0); + function handleMouseMove({ + currentTarget, + clientX, + clientY, + }: ReactMouseEvent) { + let { left, top } = currentTarget.getBoundingClientRect(); + + mouseX.set(clientX - left); + mouseY.set(clientY - top); + } + + const [isHovering, setIsHovering] = useState(false); + const handleMouseEnter = () => setIsHovering(true); + const handleMouseLeave = () => setIsHovering(false); + return ( +
+ + {isHovering && ( + + )} + + {children} +
+ ); +}; diff --git a/src/pages/Categorias.tsx b/src/pages/Categorias.tsx index d659a88..106725e 100644 --- a/src/pages/Categorias.tsx +++ b/src/pages/Categorias.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; import Layout from '@/components/layout/Layout'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { CardSpotlight } from '@/components/ui/card-spotlight'; import { getCategorySummary } from '@/services/transacao'; import { CategorySummary } from '@/types/financialTypes'; import { useToast } from "@/components/ui/use-toast"; @@ -100,20 +100,20 @@ const CategoriasPage = () => { {isLoading ? (
{[1, 2, 3, 4, 5, 6].map((i) => ( - - + +
- - +
+
- - +
+
))}
) : categories.length === 0 ? ( - - + +

Nenhuma categoria encontrada

{tipoFiltro === 'despesa' @@ -122,23 +122,23 @@ const CategoriasPage = () => { ? 'Verifique se existem transações do tipo "receita" cadastradas' : 'Verifique se existem transações cadastradas'}

- - +
+
) : ( <>
{categories.map((category) => ( - - - + +
+
{category.categoria} - - - +
+
+
{formatCurrency(category.valor)}
@@ -155,16 +155,16 @@ const CategoriasPage = () => { {(category.percentage * 100).toFixed(1)}% do total
-
-
+
+
))} - - - Resumo de Categorias {tipoFiltro !== 'all' ? (tipoFiltro === 'despesa' ? '(Despesas)' : '(Receitas)') : ''} - - + +
+

Resumo de Categorias {tipoFiltro !== 'all' ? (tipoFiltro === 'despesa' ? '(Despesas)' : '(Receitas)') : ''}

+
+
@@ -194,13 +194,5 @@ const CategoriasPage = () => {
- - - - )} -
- - ); -}; - -export default CategoriasPage; + +