diff --git a/app/(authenticated)/dashboard/_components/summar-card.tsx b/app/(authenticated)/dashboard/_components/summar-card.tsx new file mode 100644 index 0000000..61b8632 --- /dev/null +++ b/app/(authenticated)/dashboard/_components/summar-card.tsx @@ -0,0 +1,44 @@ +import AddTransactionButton from "@/app/_components/add-transaction-button"; +import { Card, CardContent, CardHeader } from "@/app/_components/ui/card"; +import { cn } from "@/app/_lib/utils"; + +interface SummaryCardProps { + title: string; + amount: string; + icon: React.ReactNode; + size?: "sm" | "lg"; +} + +const SummaryCard = ({ + title, + amount, + icon, + size = "sm", +}: SummaryCardProps) => { + return ( + + + {icon} +

+ {title} +

+
+ +

+ {Intl.NumberFormat("pt-BR", { + style: "currency", + currency: "BRL", + }).format(Number(amount))} +

+ {size === "lg" && } +
+
+ ); +}; + +export default SummaryCard; diff --git a/app/(authenticated)/dashboard/_components/summary-cards.tsx b/app/(authenticated)/dashboard/_components/summary-cards.tsx new file mode 100644 index 0000000..e33a32b --- /dev/null +++ b/app/(authenticated)/dashboard/_components/summary-cards.tsx @@ -0,0 +1,87 @@ +import { + PiggyBankIcon, + TrendingDownIcon, + TrendingUpIcon, + WalletIcon, +} from "lucide-react"; +import SummaryCard from "./summar-card"; +import { db } from "@/app/_lib/prisma"; + +interface SummaryCardsProps { + month: string; +} + +const SummaryCards = async ({ month }: SummaryCardsProps) => { + const where = { + date: { + gte: new Date(`2024-${month}-01`), + lt: new Date(`2024-${month}-31`), + }, + }; + + const deposits = await db.transactions.aggregate({ + where: { + type: "DEPOSIT", + ...where, + }, + _sum: { + amount: true, + }, + }); + const depositsTotal = deposits._sum.amount ?? 0; + + const investments = await db.transactions.aggregate({ + where: { + type: "INVESTMENT", + ...where, + }, + _sum: { + amount: true, + }, + }); + const investmentsTotal = investments._sum.amount ?? 0; + + const expenses = await db.transactions.aggregate({ + where: { + type: "EXPENSE", + ...where, + }, + _sum: { + amount: true, + }, + }); + const expensesTotal = expenses._sum.amount ?? 0; + + const balance = + Number(depositsTotal) - Number(investmentsTotal) - Number(expensesTotal); + + return ( +
+ } + size="lg" + /> +
+ } + /> + } + /> + } + /> +
+
+ ); +}; + +export default SummaryCards; diff --git a/app/(authenticated)/dashboard/_components/time-select.tsx b/app/(authenticated)/dashboard/_components/time-select.tsx new file mode 100644 index 0000000..fd509e1 --- /dev/null +++ b/app/(authenticated)/dashboard/_components/time-select.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/app/_components/ui/select"; +import { useRouter, useSearchParams } from "next/navigation"; + +interface TimeSelectProps { + defaultValue?: string; +} + +const MONTH_OPTIONS = [ + { value: "1", label: "Janeiro" }, + { value: "2", label: "Fevereiro" }, + { value: "3", label: "Março" }, + { value: "4", label: "Abril" }, + { value: "5", label: "Maio" }, + { value: "6", label: "Junho" }, + { value: "7", label: "Julho" }, + { value: "8", label: "Agosto" }, + { value: "9", label: "Setembro" }, + { value: "10", label: "Outubro" }, + { value: "11", label: "Novembro" }, + { value: "12", label: "Dezembro" }, +]; + +const TimeSelect = ({ defaultValue = "1" }: TimeSelectProps) => { + const router = useRouter(); + const searchParams = useSearchParams(); + + const handleMonthChange = (month: string) => { + const params = new URLSearchParams(searchParams); + params.set("month", month); + router.push(`/dashboard?${params.toString()}`); + }; + + return ( + + ); +}; + +export default TimeSelect; diff --git a/app/(authenticated)/dashboard/page.tsx b/app/(authenticated)/dashboard/page.tsx new file mode 100644 index 0000000..25d871a --- /dev/null +++ b/app/(authenticated)/dashboard/page.tsx @@ -0,0 +1,38 @@ +import { auth } from "@clerk/nextjs/server"; +import { redirect } from "next/navigation"; +import SummaryCards from "./_components/summary-cards"; +import TimeSelect from "./_components/time-select"; + +interface HomeProps { + searchParams: { + month?: string; + }; +} + +const Home = async ({ searchParams: { month = "1" } }: HomeProps) => { + const { userId } = await auth(); + + if (!userId) { + redirect("/login"); + } + + // Validação do mês + const monthNumber = Number(month); + const isValidMonth = monthNumber >= 1 && monthNumber <= 12; + + if (!isValidMonth) { + redirect("/dashboard?month=1"); + } + + return ( +
+
+

Dashboard

+ +
+ +
+ ); +}; + +export default Home; diff --git a/app/(authenticated)/page.tsx b/app/(authenticated)/page.tsx deleted file mode 100644 index 499de4a..0000000 --- a/app/(authenticated)/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { auth } from "@clerk/nextjs/server"; -import { redirect } from "next/navigation"; - -const Home = async () => { - const { userId } = await auth(); - - if (!userId) { - redirect("/login"); - } - - return ( -
-

Home

-
- ); -}; - -export default Home; diff --git a/app/_components/Header.tsx b/app/_components/Header.tsx index 39672a1..f88dc92 100644 --- a/app/_components/Header.tsx +++ b/app/_components/Header.tsx @@ -11,7 +11,7 @@ const Header = ({ isEnabled = true }: { isEnabled?: boolean }) => { const navigation = [ { label: "Dashboard", - href: "/", + href: "/dashboard", }, { label: "Transações", @@ -28,7 +28,7 @@ const Header = ({ isEnabled = true }: { isEnabled?: boolean }) => { return (
- + logo @@ -39,8 +39,8 @@ const Header = ({ isEnabled = true }: { isEnabled?: boolean }) => { href={item.href} className={`text-sm transition-colors ${ pathname === item.href - ? "text-foreground" - : "text-muted-foreground hover:text-foreground" + ? "font-medium text-primary" + : "text-muted-foreground hover:text-primary" }`} > {item.label} diff --git a/app/_components/ui/card.tsx b/app/_components/ui/card.tsx new file mode 100644 index 0000000..a6c236e --- /dev/null +++ b/app/_components/ui/card.tsx @@ -0,0 +1,86 @@ +import * as React from "react"; + +import { cn } from "@/app/_lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/app/global-error.tsx b/app/global-error.tsx index c3e77ec..4b0d157 100644 --- a/app/global-error.tsx +++ b/app/global-error.tsx @@ -90,7 +90,7 @@ export default function GlobalError({ Tentar novamente