From 35abe1b0bfcec94fe52b1cc0c5f451b4b36b793f Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Sun, 31 May 2026 15:18:43 -0300 Subject: [PATCH] feat(dashboard): refina experiencia dos widgets --- src/app/(dashboard)/dashboard/page.tsx | 10 +- src/features/dashboard/bills/bills-queries.ts | 1 + .../category-breakdown-chart.tsx | 4 +- .../category-breakdown-list-item.tsx | 69 +++--- .../category-breakdown-list.tsx | 7 +- .../category-breakdown-widget-view.tsx | 4 +- .../components/dashboard-grid-editable.tsx | 101 +++++++-- .../components/dashboard-metrics-cards.tsx | 50 ++++- .../goals-progress/goals-progress-item.tsx | 102 +++++---- .../goals-progress/goals-progress-list.tsx | 9 +- .../installment-expense-list-item.tsx | 45 ++-- .../installment-expenses-list.tsx | 6 +- .../components/invoices/invoice-list-item.tsx | 199 ++++++++++-------- .../invoices/invoices-widget-view.tsx | 4 +- .../components/notes/note-list-item.tsx | 79 ++++--- .../components/notes/notes-widget-view.tsx | 2 +- .../payment-breakdown-list-item.tsx | 18 +- .../payment-breakdown-list.tsx | 10 +- .../payment-overview-widget-view.tsx | 2 +- .../payment-status-category-section.tsx | 42 +++- .../payment-status-widget-view.tsx | 4 +- .../widgets/category-trends-widget.tsx | 37 +++- .../components/widgets/inbox-widget.tsx | 122 +++++++---- .../widgets/income-expense-balance-widget.tsx | 83 ++++---- .../components/widgets/my-accounts-widget.tsx | 123 +++++------ .../components/widgets/payers-widget.tsx | 49 +++-- .../widgets/purchases-by-category-widget.tsx | 22 +- .../widgets/recurring-expenses-widget.tsx | 57 ++--- .../widgets/spending-overview-widget.tsx | 7 +- .../widgets/top-establishments-widget.tsx | 12 +- .../widgets/top-expenses-widget.tsx | 85 ++------ .../widgets/widget-settings-dialog.tsx | 8 +- .../expenses/installment-expenses-helpers.ts | 6 +- .../dashboard/fetch-dashboard-data.ts | 1 - .../dashboard/invoices/invoices-helpers.ts | 29 ++- .../dashboard/lib/accounts-queries.ts | 10 +- .../dashboard/lib/extract-logo-names.ts | 2 - .../current-period-overview-queries.ts | 11 +- .../widget-registry/widget-config.tsx | 47 ++--- 39 files changed, 887 insertions(+), 592 deletions(-) diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index a4f1fc8..bf98c6f 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -27,6 +27,10 @@ export default async function Page({ searchParams }: PageProps) { const { dashboardData, preferences, quickActionOptions } = await fetchDashboardPageData(user.id, selectedPeriod); const { dashboardWidgets } = preferences; + const adminPayerSlug = + quickActionOptions.payerOptions.find( + (option) => option.value === quickActionOptions.defaultPayerId, + )?.slug ?? null; const logoMappings = await prefetchLogoMappings( user.id, @@ -37,7 +41,11 @@ export default async function Page({ searchParams }: PageProps) {
- + +
-
+
{chartData.map((entry, index) => (
@@ -31,8 +31,9 @@ export function CategoryBreakdownListItem({ category, periodParam, config, + position, }: CategoryBreakdownListItemProps) { - const hasBudget = category.budgetAmount !== null; + const hasBudget = config.showBudget && category.budgetAmount !== null; const budgetExceeded = hasBudget && category.budgetUsedPercentage !== null && @@ -44,7 +45,10 @@ export function CategoryBreakdownListItem({ return (
-
+
+ + {position} +
{category.categoryName} -
@@ -71,36 +71,29 @@ export function CategoryBreakdownListItem({ )}{" "} da {config.shareLabel} - {hasBudget && category.budgetUsedPercentage !== null ? ( - <> - · - - {budgetExceeded ? ( - <> - Excedeu{" "} - - {formatCurrency(exceededAmount)} - - - ) : ( - <> - {formatPercentage( - category.budgetUsedPercentage, - config.percentageDigits, - )}{" "} - do limite - {config.includeBudgetAmount && - category.budgetAmount !== null - ? ` ${formatCurrency(category.budgetAmount)}` - : ""} - - )} - - - ) : null}
+ {hasBudget && category.budgetUsedPercentage !== null ? ( +
+ {budgetExceeded ? ( + <> + Limite excedido em{" "} + + {formatCurrency(exceededAmount)} + + + ) : ( + <> + {formatPercentage( + category.budgetUsedPercentage, + config.percentageDigits, + )}{" "} + do limite utilizado + + )} +
+ ) : null}
diff --git a/src/features/dashboard/components/category-breakdown/category-breakdown-list.tsx b/src/features/dashboard/components/category-breakdown/category-breakdown-list.tsx index f1d22f4..8574994 100644 --- a/src/features/dashboard/components/category-breakdown/category-breakdown-list.tsx +++ b/src/features/dashboard/components/category-breakdown/category-breakdown-list.tsx @@ -5,7 +5,7 @@ type CategoryBreakdownListConfig = { shareLabel: string; percentageDigits: number; positiveTrend: "up" | "down"; - includeBudgetAmount: boolean; + showBudget: boolean; }; type CategoryBreakdownListProps = { @@ -20,13 +20,14 @@ export function CategoryBreakdownList({ config, }: CategoryBreakdownListProps) { return ( -
- {categories.map((category) => ( +
+ {categories.map((category, index) => ( ))}
diff --git a/src/features/dashboard/components/category-breakdown/category-breakdown-widget-view.tsx b/src/features/dashboard/components/category-breakdown/category-breakdown-widget-view.tsx index df8e029..8f577bf 100644 --- a/src/features/dashboard/components/category-breakdown/category-breakdown-widget-view.tsx +++ b/src/features/dashboard/components/category-breakdown/category-breakdown-widget-view.tsx @@ -34,7 +34,7 @@ const VARIANT_CONFIG = { shareLabel: "receita total", percentageDigits: 1, positiveTrend: "up", - includeBudgetAmount: true, + showBudget: false, }, expense: { emptyTitle: "Nenhuma despesa encontrada", @@ -43,7 +43,7 @@ const VARIANT_CONFIG = { shareLabel: "despesa total", percentageDigits: 0, positiveTrend: "down", - includeBudgetAmount: false, + showBudget: true, }, } as const; diff --git a/src/features/dashboard/components/dashboard-grid-editable.tsx b/src/features/dashboard/components/dashboard-grid-editable.tsx index 6c080a6..0e17a5e 100644 --- a/src/features/dashboard/components/dashboard-grid-editable.tsx +++ b/src/features/dashboard/components/dashboard-grid-editable.tsx @@ -21,6 +21,7 @@ import { RiCloseLine, RiDragMove2Line, RiEyeOffLine, + RiSettings4Line, RiTodoLine, } from "@remixicon/react"; import { useMemo, useState, useTransition } from "react"; @@ -41,6 +42,12 @@ import { import { NoteDialog } from "@/features/notes/components/note-dialog"; import { TransactionDialog } from "@/features/transactions/components/dialogs/transaction-dialog/transaction-dialog"; import { Button } from "@/shared/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/shared/components/ui/dropdown-menu"; import { ExpandableWidgetCard } from "@/shared/components/widgets/expandable-widget-card"; type DashboardGridEditableProps = { @@ -60,6 +67,9 @@ export function DashboardGridEditable({ }: DashboardGridEditableProps) { const [isEditing, setIsEditing] = useState(false); const [isPending, startTransition] = useTransition(); + const [isMobileIncomeOpen, setIsMobileIncomeOpen] = useState(false); + const [isMobileExpenseOpen, setIsMobileExpenseOpen] = useState(false); + const [isMobileNoteOpen, setIsMobileNoteOpen] = useState(false); // Initialize widget order and hidden state const [widgetOrder, setWidgetOrder] = useState( @@ -132,14 +142,6 @@ export function DashboardGridEditable({ : [...hiddenWidgets, widgetId]; setHiddenWidgets(newHidden); - - // Salvar automaticamente ao toggle - startTransition(async () => { - await updateWidgetPreferences({ - order: widgetOrder, - hidden: newHidden, - }); - }); }; const handleHideWidget = (widgetId: string) => { @@ -182,6 +184,8 @@ export function DashboardGridEditable({ setWidgetOrder(DEFAULT_WIDGET_ORDER); setHiddenWidgets([]); setMyAccountsShowExcluded(true); + setOriginalOrder(DEFAULT_WIDGET_ORDER); + setOriginalHidden([]); toast.success("Preferências restauradas!"); } else { toast.error(result.error ?? "Erro ao restaurar"); @@ -195,7 +199,68 @@ export function DashboardGridEditable({
{!isEditing ? (
-
+
+ + + + + + setIsMobileIncomeOpen(true)} + > + + Nova receita + + setIsMobileExpenseOpen(true)} + > + + Nova despesa + + setIsMobileNoteOpen(true)}> + + Nova anotação + + + + + + +
+
{isEditing ? ( <> +
)} diff --git a/src/features/dashboard/components/dashboard-metrics-cards.tsx b/src/features/dashboard/components/dashboard-metrics-cards.tsx index 138078d..01eeafd 100644 --- a/src/features/dashboard/components/dashboard-metrics-cards.tsx +++ b/src/features/dashboard/components/dashboard-metrics-cards.tsx @@ -1,9 +1,11 @@ import { RiArrowLeftRightLine, RiArrowRightDownLine, + RiArrowRightLine, RiArrowRightUpLine, RiCalendar2Line, } from "@remixicon/react"; +import Link from "next/link"; import { MetricsCardInfoButton } from "@/features/dashboard/components/metrics-card-info-button"; import { PercentageChangeIndicator } from "@/features/dashboard/components/percentage-change-indicator"; import type { DashboardCardMetrics } from "@/features/dashboard/overview/dashboard-metrics-queries"; @@ -18,10 +20,13 @@ import { } from "@/shared/components/ui/card"; import { Separator } from "@/shared/components/ui/separator"; import { formatPercentage } from "@/shared/utils/percentage"; +import { formatPeriodForUrl } from "@/shared/utils/period"; import { cn } from "@/shared/utils/ui"; type DashboardMetricsCardsProps = { metrics: DashboardCardMetrics; + period: string; + adminPayerSlug: string | null; }; type Trend = "up" | "down" | "flat"; @@ -36,6 +41,7 @@ const CARDS = [ icon: RiArrowRightDownLine, invertTrend: false, iconClass: "text-success", + transactionType: "receita", helpTitle: "Como calculamos receitas", helpLines: [ "Somamos os lançamentos do tipo Receita no período selecionado.", @@ -53,6 +59,7 @@ const CARDS = [ icon: RiArrowRightUpLine, invertTrend: true, iconClass: "text-destructive", + transactionType: "despesa", helpTitle: "Como calculamos despesas", helpLines: [ "Somamos os lançamentos do tipo Despesa no período selecionado.", @@ -70,6 +77,7 @@ const CARDS = [ icon: RiArrowLeftRightLine, invertTrend: false, iconClass: "text-warning", + transactionType: null, helpTitle: "Como calculamos o balanço", helpLines: [ "Partimos de receitas menos despesas do período.", @@ -86,6 +94,7 @@ const CARDS = [ icon: RiCalendar2Line, invertTrend: false, iconClass: "text-cyan-600", + transactionType: null, helpTitle: "Como calculamos o previsto", helpLines: [ "Acumulamos o balanço mês a mês até o período atual.", @@ -123,7 +132,11 @@ const getPercentChange = (current: number, previous: number): string | null => { }); }; -export function DashboardMetricsCards({ metrics }: DashboardMetricsCardsProps) { +export function DashboardMetricsCards({ + metrics, + period, + adminPayerSlug, +}: DashboardMetricsCardsProps) { return (
{CARDS.map( @@ -134,6 +147,7 @@ export function DashboardMetricsCards({ metrics }: DashboardMetricsCardsProps) { icon: Icon, invertTrend, iconClass, + transactionType, helpTitle, helpLines, }) => { @@ -143,19 +157,33 @@ export function DashboardMetricsCards({ metrics }: DashboardMetricsCardsProps) { metric.current, metric.previous, ); + const transactionsHref = transactionType + ? `/transactions?periodo=${formatPeriodForUrl(period)}&type=${transactionType}${adminPayerSlug ? `&payer=${adminPayerSlug}` : ""}` + : null; return ( - + - - - {label} - - +
+ + + {label} + + + {transactionsHref ? ( + + + + ) : null} +
{subtitle} diff --git a/src/features/dashboard/components/goals-progress/goals-progress-item.tsx b/src/features/dashboard/components/goals-progress/goals-progress-item.tsx index 20ef571..09865bb 100644 --- a/src/features/dashboard/components/goals-progress/goals-progress-item.tsx +++ b/src/features/dashboard/components/goals-progress/goals-progress-item.tsx @@ -1,5 +1,5 @@ import { RiPencilLine } from "@remixicon/react"; -import { PercentageChangeIndicator } from "@/features/dashboard/components/percentage-change-indicator"; +import Link from "next/link"; import { clampGoalProgress, formatGoalProgressPercentage, @@ -9,24 +9,28 @@ import { CategoryIconBadge } from "@/shared/components/entity-avatar"; import MoneyValues from "@/shared/components/money-values"; import { Button } from "@/shared/components/ui/button"; import { Progress } from "@/shared/components/ui/progress"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/shared/components/ui/tooltip"; +import { cn } from "@/shared/utils"; +import { formatPeriodForUrl } from "@/shared/utils/period"; type GoalProgressItemProps = { item: GoalProgressItemData; - index: number; onEdit: (item: GoalProgressItemData) => void; }; -export function GoalProgressItem({ - item, - index, - onEdit, -}: GoalProgressItemProps) { +export function GoalProgressItem({ item, onEdit }: GoalProgressItemProps) { const progressValue = clampGoalProgress(item.usedPercentage, 0, 100); - const percentageDelta = item.usedPercentage - 100; const isExceeded = item.status === "exceeded"; + const isCritical = item.status === "critical"; + const exceededAmount = Math.max(item.spentAmount - item.budgetAmount, 0); + const usedPercentageLabel = formatGoalProgressPercentage(item.usedPercentage); return ( -
+
  • -

    - {item.categoryName} -

    + {item.categoryId ? ( + + {item.categoryName} + + ) : ( +

    + {item.categoryName} +

    + )}

    {" "} de{" "} - + · + + {isExceeded ? ( + <> + acima do limite + + ) : ( + `${usedPercentageLabel} utilizado` + )} +

    -
    - -
    + + + + + Atualizar orçamento +
    -
  • + ); } diff --git a/src/features/dashboard/components/goals-progress/goals-progress-list.tsx b/src/features/dashboard/components/goals-progress/goals-progress-list.tsx index fee590c..0d4efe8 100644 --- a/src/features/dashboard/components/goals-progress/goals-progress-list.tsx +++ b/src/features/dashboard/components/goals-progress/goals-progress-list.tsx @@ -21,13 +21,8 @@ export function GoalsProgressList({ items, onEdit }: GoalsProgressListProps) { return (
      - {items.map((item, index) => ( - + {items.map((item) => ( + ))}
    ); diff --git a/src/features/dashboard/components/installment-expenses/installment-expense-list-item.tsx b/src/features/dashboard/components/installment-expenses/installment-expense-list-item.tsx index 1f5987c..3867305 100644 --- a/src/features/dashboard/components/installment-expenses/installment-expense-list-item.tsx +++ b/src/features/dashboard/components/installment-expenses/installment-expense-list-item.tsx @@ -9,6 +9,7 @@ import { TooltipContent, TooltipTrigger, } from "@/shared/components/ui/tooltip"; +import { getPaymentMethodIcon } from "@/shared/utils/icons"; type InstallmentExpenseListItemProps = { expense: InstallmentExpense; @@ -28,7 +29,7 @@ export function InstallmentExpenseListItem({ } = buildInstallmentExpenseDisplay(expense); return ( -
    +
    @@ -66,22 +67,32 @@ export function InstallmentExpenseListItem({ />
    - {remainingInstallments === 0 ? ( -

    - {endDate ? `Termina em ${endDate}` : null} - {" · Quitado"} -

    - ) : ( -

    - {endDate ? `Termina em ${endDate}` : null} - {` · ${remainingLabel}: `} - {" "} - ({remainingInstallments}x) -

    - )} +
    + + + {getPaymentMethodIcon(expense.paymentMethod)} + {expense.paymentMethod} + + {endDate ? Até {endDate} : null} + + + {remainingInstallments === 0 ? ( + "Quitado" + ) : ( + <> + {remainingLabel}:{" "} + {" "} + ({remainingInstallments}x) + + )} + +
    diff --git a/src/features/dashboard/components/installment-expenses/installment-expenses-list.tsx b/src/features/dashboard/components/installment-expenses/installment-expenses-list.tsx index 5062ed1..1c709db 100644 --- a/src/features/dashboard/components/installment-expenses/installment-expenses-list.tsx +++ b/src/features/dashboard/components/installment-expenses/installment-expenses-list.tsx @@ -14,17 +14,17 @@ export function InstallmentExpensesList({ return ( } - title="Nenhuma despesa parcelada" + title="Nenhuma despesa parcelada encontrada" description="Lançamentos parcelados aparecerão aqui conforme forem registrados." /> ); } return ( -
      +
      {expenses.map((expense) => ( ))} -
    +
    ); } diff --git a/src/features/dashboard/components/invoices/invoice-list-item.tsx b/src/features/dashboard/components/invoices/invoice-list-item.tsx index 3c08a97..afc7761 100644 --- a/src/features/dashboard/components/invoices/invoice-list-item.tsx +++ b/src/features/dashboard/components/invoices/invoice-list-item.tsx @@ -1,10 +1,11 @@ -import { RiCheckboxCircleFill, RiExternalLinkLine } from "@remixicon/react"; +import { RiCheckboxCircleFill, RiGroupLine } from "@remixicon/react"; import Link from "next/link"; import { PercentageChangeIndicator } from "@/features/dashboard/components/percentage-change-indicator"; import { buildInvoiceDetailsHref, buildInvoiceInitials, formatInvoicePaymentDate, + formatInvoiceWidgetOverdueLabel, formatInvoiceWidgetPaymentDate, getInvoiceShareLabel, parseInvoiceDueDate, @@ -48,9 +49,13 @@ export function InvoiceListItem({ invoice, onPay }: InvoiceListItemProps) { const absolutePaymentInfo = formatInvoicePaymentDate(invoice.paidAt); const breakdown = invoice.pagadorBreakdown ?? []; const hasBreakdown = breakdown.length > 0; + const hasMultiplePayers = breakdown.length > 1; const detailHref = buildInvoiceDetailsHref(invoice.cardId, invoice.period); + const overdueLabel = formatInvoiceWidgetOverdueLabel(dueInfo.date); const dueTooltipLabel = - dueInfo.label !== absoluteDueInfo.label ? absoluteDueInfo.label : null; + overdueLabel || dueInfo.label !== absoluteDueInfo.label + ? absoluteDueInfo.label + : null; const paymentTooltipLabel = paymentInfo?.label && paymentInfo.label !== absolutePaymentInfo?.label ? absolutePaymentInfo?.label @@ -63,15 +68,11 @@ export function InvoiceListItem({ invoice, onPay }: InvoiceListItemProps) { className="inline-flex max-w-full items-center gap-1 text-sm font-medium text-foreground underline-offset-2 hover:text-primary hover:underline" > {invoice.cardName} - ); return ( -
    +
  • - {hasBreakdown ? ( - - {linkNode} - -

    - Distribuição por pessoa -

    -
      - {breakdown.map((share, index) => ( -
    • - - - - {buildInvoiceInitials(share.pagadorName)} - - -
      -

      - {share.pagadorName} -

      -

      - {getInvoiceShareLabel( - share.amount, - Math.abs(invoice.totalAmount), - )} -

      -
      -
      - - -
      -
    • - ))} -
    -
    -
    - ) : ( - linkNode - )} +
    + {hasBreakdown ? ( + + {linkNode} + +

    + Distribuição por pessoa +

    +
      + {breakdown.map((share, index) => ( +
    • + + + + {buildInvoiceInitials(share.pagadorName)} + + +
      +

      + {share.pagadorName} +

      +

      + {getInvoiceShareLabel( + share.amount, + Math.abs(invoice.totalAmount), + )} +

      +
      +
      + + +
      +
    • + ))} +
    +
    +
    + ) : ( + linkNode + )} + {hasMultiplePayers ? ( + + + + + Ver distribuição por pessoa + + + + Ver distribuição por pessoa + + + ) : null} +
    {!isPaid ? ( dueTooltipLabel ? ( - {dueInfo.label} + + {overdueLabel ?? dueInfo.label} + {dueTooltipLabel} ) : ( - {dueInfo.label} + + {overdueLabel ?? dueInfo.label} + ) ) : null} {isPaid && paymentInfo ? ( @@ -174,30 +204,31 @@ export function InvoiceListItem({ invoice, onPay }: InvoiceListItemProps) { className="font-medium" amount={Math.abs(invoice.totalAmount)} /> - + ) : ( + Pagar + )} + + )}
    -
    +
  • ); } diff --git a/src/features/dashboard/components/invoices/invoices-widget-view.tsx b/src/features/dashboard/components/invoices/invoices-widget-view.tsx index c0e0efc..bd05f9f 100644 --- a/src/features/dashboard/components/invoices/invoices-widget-view.tsx +++ b/src/features/dashboard/components/invoices/invoices-widget-view.tsx @@ -39,9 +39,7 @@ export function InvoicesWidgetView({ }: InvoicesWidgetViewProps) { return ( <> -
    - -
    + +
  • {displayTitle}

    -
    - - {getNoteTasksSummary(note)} - +
    + {isTask ? ( + + {getNoteTasksSummary(note)} + + ) : null}

    - {createdAtLabel} + + + {createdAtLabel} +

    -
    - - +
    + + + + + Editar anotação + + + + + + Ver detalhes +
    -
    +
  • ); } diff --git a/src/features/dashboard/components/notes/notes-widget-view.tsx b/src/features/dashboard/components/notes/notes-widget-view.tsx index f226cc5..96313c8 100644 --- a/src/features/dashboard/components/notes/notes-widget-view.tsx +++ b/src/features/dashboard/components/notes/notes-widget-view.tsx @@ -27,7 +27,7 @@ export function NotesWidgetView({ }: NotesWidgetViewProps) { return ( <> -
    +
    +
    + + {position} +
    {item.title} - ) : (

    {item.title}

    )} - +
    {formatPaymentBreakdownTransactionsLabel(item.transactions)} - {formatPaymentBreakdownPercentage(item.percentage)} + + {formatPaymentBreakdownPercentage(item.percentage)} do total +
    diff --git a/src/features/dashboard/components/payment-overview/payment-breakdown-list.tsx b/src/features/dashboard/components/payment-overview/payment-breakdown-list.tsx index d4295da..ec35e92 100644 --- a/src/features/dashboard/components/payment-overview/payment-breakdown-list.tsx +++ b/src/features/dashboard/components/payment-overview/payment-breakdown-list.tsx @@ -31,10 +31,14 @@ export function PaymentBreakdownList({ } return ( -
    +
      - {items.map((item) => ( - + {items.map((item, index) => ( + ))}
    diff --git a/src/features/dashboard/components/payment-overview/payment-overview-widget-view.tsx b/src/features/dashboard/components/payment-overview/payment-overview-widget-view.tsx index 67cc0b0..c4a47b9 100644 --- a/src/features/dashboard/components/payment-overview/payment-overview-widget-view.tsx +++ b/src/features/dashboard/components/payment-overview/payment-overview-widget-view.tsx @@ -43,7 +43,7 @@ export function PaymentOverviewWidgetView({ className="text-xs data-[state=active]:bg-transparent" > - Formas + Formas de pagamento diff --git a/src/features/dashboard/components/payment-status/payment-status-category-section.tsx b/src/features/dashboard/components/payment-status/payment-status-category-section.tsx index e9a4718..dea3237 100644 --- a/src/features/dashboard/components/payment-status/payment-status-category-section.tsx +++ b/src/features/dashboard/components/payment-status/payment-status-category-section.tsx @@ -1,16 +1,18 @@ +import { RiArrowDownLine, RiArrowUpLine } from "@remixicon/react"; import StatusDot from "@/shared/components/feedback/status-dot"; import MoneyValues from "@/shared/components/money-values"; import { Progress } from "@/shared/components/ui/progress"; +import { formatPercentage } from "@/shared/utils/percentage"; type PaymentStatusCategorySectionProps = { - title: string; + type: "income" | "expenses"; total: number; confirmed: number; pending: number; }; export function PaymentStatusCategorySection({ - title, + type, total, confirmed, pending, @@ -19,27 +21,51 @@ export function PaymentStatusCategorySection({ const absConfirmed = Math.abs(confirmed); const confirmedPercentage = absTotal > 0 ? (absConfirmed / absTotal) * 100 : 0; + const income = type === "income"; + const title = income ? "A receber" : "A pagar"; + const confirmedLabel = income ? "recebidos" : "pagos"; + const pendingLabel = income ? "a receber" : "a pagar"; + const percentageLabel = income ? "recebido" : "pago"; + const TitleIcon = income ? RiArrowDownLine : RiArrowUpLine; return ( -
    +
    - {title} - + + + {title} + + + + {formatPercentage(confirmedPercentage, { + minimumFractionDigits: 0, + maximumFractionDigits: 0, + })}{" "} + {percentageLabel} + + +
    - +
    - confirmados + + {confirmedLabel} +
    - pendentes + {pendingLabel}
    diff --git a/src/features/dashboard/components/payment-status/payment-status-widget-view.tsx b/src/features/dashboard/components/payment-status/payment-status-widget-view.tsx index 49605e2..7346c95 100644 --- a/src/features/dashboard/components/payment-status/payment-status-widget-view.tsx +++ b/src/features/dashboard/components/payment-status/payment-status-widget-view.tsx @@ -28,7 +28,7 @@ export function PaymentStatusWidgetView({ return ( {category.categoryName}

    -

    - vs{" "} - +

    + + + Mês anterior: + + + + + + Mês atual: + +

    , +): string | null { + if (!sourceAppName) return null; + + const appName = sourceAppName.toLowerCase(); + if (logoMap[appName]) return resolveLogoSrc(logoMap[appName]); + + for (const [name, logo] of Object.entries(logoMap)) { + if (name.includes(appName) || appName.includes(name)) { + return resolveLogoSrc(logo); + } + } + + return null; +} + export function InboxWidget({ snapshot, quickActionOptions, @@ -149,13 +173,18 @@ export function InboxWidget({ if (snapshot.pendingCount === 0) { return ( } + icon={} title="Tudo em dia" description="Nenhum pré-lançamento aguardando revisão." /> ); } + const remainingCount = Math.max( + snapshot.pendingCount - snapshot.recentItems.length, + 0, + ); + return (
    {snapshot.recentItems.map((item) => { @@ -168,17 +197,12 @@ export function InboxWidget({ parsedAmount !== null && Number.isFinite(parsedAmount) ? parsedAmount : null; - const logoKey = item.sourceAppName?.toLowerCase() ?? ""; - const rawLogo = snapshot.logoMap[logoKey] ?? null; - const logoSrc = resolveLogoSrc(rawLogo); + const logoSrc = findMatchingLogo(item.sourceAppName, snapshot.logoMap); const displayLogo = logoSrc ?? DEFAULT_INBOX_APP_LOGO; return ( -
    -
    +
    +
    {item.sourceAppName -
    -

    - {displayName.length > 30 - ? `${displayName.slice(0, 30)}...` - : displayName} +

    +

    + {displayName}

    -
    - {item.sourceAppName && {item.sourceAppName}} +
    + {item.sourceAppName && ( + {item.sourceAppName} + )} - {relativeTime(item.createdAt)} + {relativeTime(item.notificationTimestamp)}
    -
    +
    {amount !== null && ( )} + {amount === null && ( + + Valor não identificado + + )}
    - - + + + + + Lançar + + + + + + Descartar +
    ); })} + {remainingCount > 0 && ( + + + {remainingCount} pendentes · Revisar todos + + )} + ({ - month: month.monthLabel, + month: formatCompactPeriodLabel(month.month).toLowerCase(), receita: month.income, despesa: month.expense, balanco: month.balance, @@ -59,16 +67,18 @@ export function IncomeExpenseBalanceWidget({ } return ( - + - + + {month ? ( +

    + {month} +

    + ) : null}
    {payload.map((entry) => { const config = @@ -111,7 +128,7 @@ export function IncomeExpenseBalanceWidget({
    ); }} - cursor={{ fill: "hsl(var(--muted))", opacity: 0.3 }} + cursor={{ fill: "var(--muted)", opacity: 0.3 }} /> - -
    +
    -
    -
    -
    - - {chartConfig.receita.label} - -
    -
    -
    - - {chartConfig.despesa.label} - -
    -
    -
    - - {chartConfig.balanco.label} - -
    +
    + {Object.values(chartConfig).map((config) => ( +
    +
    + {config.label} +
    + ))}
    ); diff --git a/src/features/dashboard/components/widgets/my-accounts-widget.tsx b/src/features/dashboard/components/widgets/my-accounts-widget.tsx index 6ce110e..af87e60 100644 --- a/src/features/dashboard/components/widgets/my-accounts-widget.tsx +++ b/src/features/dashboard/components/widgets/my-accounts-widget.tsx @@ -1,8 +1,8 @@ "use client"; import { + RiArrowRightLine, RiBarChartBoxLine, - RiExternalLinkLine, RiEyeLine, RiEyeOffLine, } from "@remixicon/react"; @@ -24,7 +24,9 @@ import { import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state"; import { isAccountInactive } from "@/shared/lib/accounts/constants"; import { resolveLogoSrc } from "@/shared/lib/logo"; +import { buildInitials } from "@/shared/utils/initials"; import { formatPeriodForUrl } from "@/shared/utils/period"; +import { cn } from "@/shared/utils/ui"; type MyAccountsWidgetProps = { accounts: DashboardAccount[]; @@ -54,9 +56,6 @@ export function MyAccountsWidget({ : activeAccounts.filter((account) => !account.excludeFromBalance); const displayedAccounts = visibleAccounts.slice(0, 5); const remainingCount = visibleAccounts.length - displayedAccounts.length; - const hiddenExcludedAccountsCount = showExcludedAccounts - ? 0 - : excludedAccountsCount; const toggleButtonLabel = showExcludedAccounts ? "Ocultar contas não consideradas" : "Mostrar contas não consideradas"; @@ -81,7 +80,7 @@ export function MyAccountsWidget({ <>
    -

    Saldo Total

    +

    Saldo total

    @@ -106,51 +105,46 @@ export function MyAccountsWidget({

    {toggleButtonLabel}

    + {!showExcludedAccounts ? ( +

    + {excludedAccountsCount}{" "} + {excludedAccountsCount === 1 + ? "conta não considerada oculta" + : "contas não consideradas ocultas"} +

    + ) : null}
    ) : null}
    - {hiddenExcludedAccountsCount > 0 ? ( -

    - {hiddenExcludedAccountsCount}{" "} - {hiddenExcludedAccountsCount === 1 - ? "conta não considerada oculta" - : "contas não consideradas ocultas"} -

    - ) : null} -
    {activeAccounts.length === 0 ? ( -
    - - } - title="Você ainda não adicionou nenhuma conta" - description="Cadastre suas contas bancárias para acompanhar os saldos e movimentações." - /> -
    + + } + title="Você ainda não adicionou nenhuma conta" + description="Cadastre suas contas bancárias para acompanhar os saldos e movimentações." + /> ) : displayedAccounts.length === 0 ? ( -
    - } - title="As contas não consideradas estão ocultas" - description="Use o botão no topo do widget para mostrá-las novamente." - /> -
    + } + title="As contas não consideradas estão ocultas" + description="Use o botão no topo do widget para mostrá-las novamente." + /> ) : (
      {displayedAccounts.map((account, index) => { const logoSrc = resolveLogoSrc(account.logo); return ( -
      -
      +
      {logoSrc ? ( - ) : null} + ) : ( + + {buildInitials(account.name)} + + )}
      @@ -172,44 +170,41 @@ export function MyAccountsWidget({ className="inline-flex max-w-full items-center gap-1 text-sm font-medium text-foreground underline-offset-2 hover:text-primary hover:underline" > {account.name} - - {account.excludeFromBalance ? ( - - - - - Não considerada - - - - -

      - Esta conta aparece na lista, mas não entra no - cálculo do saldo total porque está marcada para - desconsiderar do saldo total. -

      -
      -
      - ) : null} -
      {account.accountType} + {account.excludeFromBalance ? ( + + + + + Não considerada + + + + +

      + Esta conta aparece na lista, mas não entra no + cálculo do saldo total. +

      +
      +
      + ) : null}
      -
      + ); })}
    @@ -217,8 +212,14 @@ export function MyAccountsWidget({
    {remainingCount > 0 ? ( - - +{remainingCount} contas não exibidas + + + +{remainingCount} contas não exibidas + + ) : null} diff --git a/src/features/dashboard/components/widgets/payers-widget.tsx b/src/features/dashboard/components/widgets/payers-widget.tsx index 258b808..d2563b5 100644 --- a/src/features/dashboard/components/widgets/payers-widget.tsx +++ b/src/features/dashboard/components/widgets/payers-widget.tsx @@ -1,10 +1,6 @@ "use client"; -import { - RiExternalLinkLine, - RiGroupLine, - RiVerifiedBadgeFill, -} from "@remixicon/react"; +import { RiGroupLine, RiVerifiedBadgeFill } from "@remixicon/react"; import Link from "next/link"; import { PercentageChangeIndicator } from "@/features/dashboard/components/percentage-change-indicator"; import type { DashboardPagador } from "@/features/dashboard/lib/payers-queries"; @@ -14,6 +10,11 @@ import { AvatarFallback, AvatarImage, } from "@/shared/components/ui/avatar"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/shared/components/ui/tooltip"; import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state"; import { getAvatarSrc } from "@/shared/lib/payers/utils"; import { buildInitials } from "@/shared/utils/initials"; @@ -33,7 +34,7 @@ export function PayersWidget({ payers }: PayersWidgetProps) { /> ) : (
    - {payers.map((payer) => { + {payers.map((payer, index) => { const initials = buildInitials(payer.name); const hasValidPercentageChange = typeof payer.percentageChange === "number" && @@ -45,8 +46,11 @@ export function PayersWidget({ payers }: PayersWidgetProps) { return (
    + + {index + 1} +
    {payer.name} {payer.isAdmin && ( - + + + + + Pessoa principal + + + + Pessoa principal + + )} -

    - {payer.email ?? "Sem email cadastrado"} + Despesas no período

    @@ -85,7 +95,12 @@ export function PayersWidget({ payers }: PayersWidgetProps) { className="font-medium" amount={payer.totalExpenses} /> - +
    + + {percentageChange !== null ? ( + vs. mês ant. + ) : null} +
    ); diff --git a/src/features/dashboard/components/widgets/purchases-by-category-widget.tsx b/src/features/dashboard/components/widgets/purchases-by-category-widget.tsx index 2f12dcd..d57089e 100644 --- a/src/features/dashboard/components/widgets/purchases-by-category-widget.tsx +++ b/src/features/dashboard/components/widgets/purchases-by-category-widget.tsx @@ -1,6 +1,6 @@ "use client"; -import { RiArrowDownSFill, RiStore3Line } from "@remixicon/react"; +import { RiFileList2Line, RiStore3Line } from "@remixicon/react"; import { useEffect, useMemo, useRef, useState } from "react"; import type { PurchasesByCategoryData } from "@/features/dashboard/categories/purchases-by-category-queries"; import { EstablishmentLogo } from "@/shared/components/entity-avatar"; @@ -8,7 +8,9 @@ import MoneyValues from "@/shared/components/money-values"; import { Select, SelectContent, + SelectGroup, SelectItem, + SelectLabel, SelectTrigger, SelectValue, } from "@/shared/components/ui/select"; @@ -129,18 +131,18 @@ export function PurchasesByCategoryWidget({ {Object.entries(categoriesByType).map(([type, categories]) => ( -
    -
    + + {CATEGORY_TYPE_LABEL[ type as keyof typeof CATEGORY_TYPE_LABEL ] ?? type} -
    + {categories.map((category) => ( {category.name} ))} -
    + ))}
    @@ -148,12 +150,12 @@ export function PurchasesByCategoryWidget({ {currentTransactions.length === 0 ? ( } - title="Nenhuma compra encontrada" + icon={} + title="Nenhum lançamento encontrado" description={ selectedCategory ? `Não há lançamentos na categoria "${selectedCategory.name}".` - : "Selecione uma categoria para visualizar as compras." + : "Selecione uma categoria para visualizar os lançamentos." } /> ) : ( @@ -162,9 +164,9 @@ export function PurchasesByCategoryWidget({ return (
    -
    +
    diff --git a/src/features/dashboard/components/widgets/recurring-expenses-widget.tsx b/src/features/dashboard/components/widgets/recurring-expenses-widget.tsx index b2a937f..9f9437d 100644 --- a/src/features/dashboard/components/widgets/recurring-expenses-widget.tsx +++ b/src/features/dashboard/components/widgets/recurring-expenses-widget.tsx @@ -3,6 +3,7 @@ import type { RecurringExpensesData } from "@/features/dashboard/expenses/recurr import { EstablishmentLogo } from "@/shared/components/entity-avatar"; import MoneyValues from "@/shared/components/money-values"; import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state"; +import { getPaymentMethodIcon } from "@/shared/utils/icons"; type RecurringExpensesWidgetProps = { data: RecurringExpensesData; @@ -10,10 +11,10 @@ type RecurringExpensesWidgetProps = { const formatOccurrences = (value: number | null) => { if (!value) { - return "Recorrência contínua"; + return "Repete mensalmente"; } - return `${value} recorrências mensais`; + return `Repete por ${value} ${value === 1 ? "mês" : "meses"}`; }; export function RecurringExpensesWidget({ @@ -23,7 +24,7 @@ export function RecurringExpensesWidget({ return ( } - title="Nenhuma despesa recorrente" + title="Nenhuma despesa recorrente encontrada" description="Lançamentos recorrentes aparecerão aqui conforme forem registrados." /> ); @@ -31,33 +32,39 @@ export function RecurringExpensesWidget({ return (
    - {data.expenses.map((expense) => { - return ( -
    - + {[...data.expenses] + .sort((a, b) => b.amount - a.amount) + .map((expense) => { + return ( +
    + -
    -
    -

    - {expense.name} -

    +
    +
    +

    + {expense.name} +

    - -
    + +
    -
    - - {expense.paymentMethod} - - {formatOccurrences(expense.recurrenceCount)} +
    + + {getPaymentMethodIcon(expense.paymentMethod)} + {expense.paymentMethod} + + {formatOccurrences(expense.recurrenceCount)} +
    -
    - ); - })} + ); + })}
    ); } diff --git a/src/features/dashboard/components/widgets/spending-overview-widget.tsx b/src/features/dashboard/components/widgets/spending-overview-widget.tsx index 7430044..75cc4ff 100644 --- a/src/features/dashboard/components/widgets/spending-overview-widget.tsx +++ b/src/features/dashboard/components/widgets/spending-overview-widget.tsx @@ -15,13 +15,11 @@ import { TopExpensesWidget } from "./top-expenses-widget"; type SpendingOverviewWidgetProps = { topExpensesAll: TopExpensesData; - topExpensesCardOnly: TopExpensesData; topEstablishmentsData: TopEstablishmentsData; }; export function SpendingOverviewWidget({ topExpensesAll, - topExpensesCardOnly, topEstablishmentsData, }: SpendingOverviewWidgetProps) { const [activeTab, setActiveTab] = useState<"expenses" | "establishments">( @@ -54,10 +52,7 @@ export function SpendingOverviewWidget({ - + diff --git a/src/features/dashboard/components/widgets/top-establishments-widget.tsx b/src/features/dashboard/components/widgets/top-establishments-widget.tsx index 29ee1bb..a98fa63 100644 --- a/src/features/dashboard/components/widgets/top-establishments-widget.tsx +++ b/src/features/dashboard/components/widgets/top-establishments-widget.tsx @@ -28,13 +28,16 @@ export function TopEstablishmentsWidget({ /> ) : (
    - {data.establishments.map((establishment) => { + {data.establishments.map((establishment, index) => { return (
    -
    + + {index + 1} + +
    @@ -42,7 +45,8 @@ export function TopEstablishmentsWidget({ {establishment.name}

    - {formatOccurrencesLabel(establishment.occurrences)} + {formatOccurrencesLabel(establishment.occurrences)} · + total acumulado

    diff --git a/src/features/dashboard/components/widgets/top-expenses-widget.tsx b/src/features/dashboard/components/widgets/top-expenses-widget.tsx index 9d3b06b..53b70cd 100644 --- a/src/features/dashboard/components/widgets/top-expenses-widget.tsx +++ b/src/features/dashboard/components/widgets/top-expenses-widget.tsx @@ -1,20 +1,18 @@ "use client"; import { RiArrowUpDoubleLine } from "@remixicon/react"; -import { useMemo, useState } from "react"; +import { useMemo } from "react"; import type { TopExpense, TopExpensesData, } from "@/features/dashboard/expenses/top-expenses-queries"; import { EstablishmentLogo } from "@/shared/components/entity-avatar"; import MoneyValues from "@/shared/components/money-values"; -import { Switch } from "@/shared/components/ui/switch"; import { WidgetEmptyState } from "@/shared/components/widgets/widget-empty-state"; import { formatTransactionDate } from "@/shared/utils/date"; type TopExpensesWidgetProps = { - allExpenses: TopExpensesData; - cardOnlyExpenses: TopExpensesData; + data: TopExpensesData; }; const shouldIncludeExpense = (expense: TopExpense) => { @@ -31,75 +29,34 @@ const shouldIncludeExpense = (expense: TopExpense) => { return true; }; -const isCardExpense = (expense: TopExpense) => - expense.paymentMethod?.toLowerCase().includes("cartão") ?? false; - -export function TopExpensesWidget({ - allExpenses, - cardOnlyExpenses, -}: TopExpensesWidgetProps) { - const [cardOnly, setCardOnly] = useState(false); - const normalizedAllExpenses = useMemo(() => { - return allExpenses.expenses.filter(shouldIncludeExpense); - }, [allExpenses]); - - const normalizedCardOnlyExpenses = useMemo(() => { - const merged = [...cardOnlyExpenses.expenses, ...normalizedAllExpenses]; - const seen = new Set(); - - return merged.filter((expense) => { - if (seen.has(expense.id)) { - return false; - } - - if (!isCardExpense(expense) || !shouldIncludeExpense(expense)) { - return false; - } - - seen.add(expense.id); - return true; - }); - }, [cardOnlyExpenses, normalizedAllExpenses]); - - const data = cardOnly - ? { expenses: normalizedCardOnlyExpenses } - : { expenses: normalizedAllExpenses }; +export function TopExpensesWidget({ data }: TopExpensesWidgetProps) { + const expenses = useMemo( + () => data.expenses.filter(shouldIncludeExpense), + [data.expenses], + ); return ( -
    -
    - - + {expenses.length === 0 ? ( + + } + title="Nenhuma despesa encontrada" + description="Quando houver despesas registradas, elas aparecerão aqui." /> -
    - - {data.expenses.length === 0 ? ( -
    - - } - title="Nenhuma despesa encontrada" - description="Quando houver despesas registradas, elas aparecerão aqui." - /> -
    ) : (
    - {data.expenses.map((expense) => { + {expenses.map((expense, index) => { return (
    -
    + + {index + 1} + +
    diff --git a/src/features/dashboard/components/widgets/widget-settings-dialog.tsx b/src/features/dashboard/components/widgets/widget-settings-dialog.tsx index 52f5598..d09be62 100644 --- a/src/features/dashboard/components/widgets/widget-settings-dialog.tsx +++ b/src/features/dashboard/components/widgets/widget-settings-dialog.tsx @@ -21,6 +21,7 @@ type WidgetSettingsDialogProps = { onToggleWidget: (widgetId: string) => void; onReset: () => void; triggerClassName?: string; + triggerLabel?: string; }; export function WidgetSettingsDialog({ @@ -28,6 +29,7 @@ export function WidgetSettingsDialog({ onToggleWidget, onReset, triggerClassName, + triggerLabel = "Widgets", }: WidgetSettingsDialogProps) { const [open, setOpen] = useState(false); @@ -40,12 +42,12 @@ export function WidgetSettingsDialog({ className={cn("gap-2", triggerClassName)} > - Widgets + {triggerLabel} - Configurar Widgets + Configurar widgets Escolha quais widgets deseja exibir no seu dashboard. @@ -91,7 +93,7 @@ export function WidgetSettingsDialog({ className="gap-2" > - Restaurar Padrão + Restaurar padrão diff --git a/src/features/dashboard/expenses/installment-expenses-helpers.ts b/src/features/dashboard/expenses/installment-expenses-helpers.ts index 9733b35..6a40446 100644 --- a/src/features/dashboard/expenses/installment-expenses-helpers.ts +++ b/src/features/dashboard/expenses/installment-expenses-helpers.ts @@ -5,7 +5,7 @@ import { capitalize } from "@/shared/utils/string"; type InstallmentExpenseDisplay = { compactLabel: string | null; isLast: boolean; - remainingLabel: "Próx." | "Aberto"; + remainingLabel: "Próximas" | "Em aberto"; remainingInstallments: number; remainingAmount: number; endDate: string | null; @@ -17,7 +17,7 @@ const buildInstallmentCompactLabel = ( installmentCount: number | null, ) => { if (currentInstallment && installmentCount) { - return `${currentInstallment} de ${installmentCount}`; + return `Parcela ${currentInstallment} de ${installmentCount}`; } return null; @@ -111,7 +111,7 @@ export const buildInstallmentExpenseDisplay = ( installmentCount, ), isLast: isInstallmentLast(currentInstallment, installmentCount), - remainingLabel: isSettled === true ? "Próx." : "Aberto", + remainingLabel: isSettled === true ? "Próximas" : "Em aberto", remainingInstallments: calculateInstallmentRemainingCount( currentInstallment, installmentCount, diff --git a/src/features/dashboard/fetch-dashboard-data.ts b/src/features/dashboard/fetch-dashboard-data.ts index aa34cdc..eb2b9c5 100644 --- a/src/features/dashboard/fetch-dashboard-data.ts +++ b/src/features/dashboard/fetch-dashboard-data.ts @@ -65,7 +65,6 @@ async function fetchDashboardDataInternal(userId: string, period: string) { installmentExpensesData: currentPeriodOverview.installmentExpensesData, topEstablishmentsData: currentPeriodOverview.topEstablishmentsData, topExpensesAll: currentPeriodOverview.topExpensesAll, - topExpensesCardOnly: currentPeriodOverview.topExpensesCardOnly, purchasesByCategoryData: currentPeriodOverview.purchasesByCategoryData, incomeByCategoryData: categoryOverview.incomeByCategoryData, expensesByCategoryData: categoryOverview.expensesByCategoryData, diff --git a/src/features/dashboard/invoices/invoices-helpers.ts b/src/features/dashboard/invoices/invoices-helpers.ts index 5420724..03ab2ae 100644 --- a/src/features/dashboard/invoices/invoices-helpers.ts +++ b/src/features/dashboard/invoices/invoices-helpers.ts @@ -4,7 +4,11 @@ import { INVOICE_PAYMENT_STATUS, type InvoicePaymentStatus, } from "@/shared/lib/invoices"; -import { getBusinessDateString } from "@/shared/utils/date"; +import { + getBusinessDateString, + parseUtcDateString, + toDateOnlyString, +} from "@/shared/utils/date"; import { buildDueDateInfoFromPeriodDay, buildRelativeDueDateInfoFromPeriodDay, @@ -80,6 +84,29 @@ export const formatInvoiceWidgetPaymentDate = ( }; }; +export const formatInvoiceWidgetOverdueLabel = ( + value: string | null, +): string | null => { + const dueDateValue = toDateOnlyString(value); + const todayValue = getBusinessDateString(); + if (!dueDateValue || dueDateValue >= todayValue) { + return null; + } + + const dueDate = parseUtcDateString(dueDateValue); + const today = parseUtcDateString(todayValue); + if (!dueDate || !today) { + return null; + } + + const overdueDays = Math.round( + (today.getTime() - dueDate.getTime()) / (24 * 60 * 60 * 1000), + ); + return overdueDays === 1 + ? "Atrasada · venceu ontem" + : `Atrasada · venceu há ${overdueDays} dias`; +}; + export const getCurrentDateString = () => getBusinessDateString(); const formatInvoiceSharePercentage = (value: number) => { diff --git a/src/features/dashboard/lib/accounts-queries.ts b/src/features/dashboard/lib/accounts-queries.ts index 92488d7..0f8226f 100644 --- a/src/features/dashboard/lib/accounts-queries.ts +++ b/src/features/dashboard/lib/accounts-queries.ts @@ -1,6 +1,9 @@ import { and, eq, sql } from "drizzle-orm"; import { financialAccounts, transactions } from "@/db/schema"; -import { INITIAL_BALANCE_NOTE } from "@/shared/lib/accounts/constants"; +import { + INITIAL_BALANCE_NOTE, + isAccountInactive, +} from "@/shared/lib/accounts/constants"; import { db } from "@/shared/lib/db"; import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id"; import { safeToNumber as toNumber } from "@/shared/utils/number"; @@ -101,7 +104,10 @@ export async function fetchDashboardAccounts( .sort((a, b) => b.balance - a.balance); const totalBalance = accounts - .filter((account) => !account.excludeFromBalance) + .filter( + (account) => + !account.excludeFromBalance && !isAccountInactive(account.status), + ) .reduce((total, account) => total + account.balance, 0); return { diff --git a/src/features/dashboard/lib/extract-logo-names.ts b/src/features/dashboard/lib/extract-logo-names.ts index 25d2f7c..4a0dfc0 100644 --- a/src/features/dashboard/lib/extract-logo-names.ts +++ b/src/features/dashboard/lib/extract-logo-names.ts @@ -16,8 +16,6 @@ export function extractDashboardLogoNames(data: DashboardData): string[] { for (const establishment of data.topEstablishmentsData.establishments) names.push(establishment.name); for (const expense of data.topExpensesAll.expenses) names.push(expense.name); - for (const expense of data.topExpensesCardOnly.expenses) - names.push(expense.name); for (const transactions of Object.values( data.purchasesByCategoryData.transactionsByCategory, )) { diff --git a/src/features/dashboard/overview/current-period-overview-queries.ts b/src/features/dashboard/overview/current-period-overview-queries.ts index 7c08019..a44a9ff 100644 --- a/src/features/dashboard/overview/current-period-overview-queries.ts +++ b/src/features/dashboard/overview/current-period-overview-queries.ts @@ -34,7 +34,6 @@ import { import { safeToNumber as toNumber } from "@/shared/utils/number"; const PAYMENT_METHOD_BOLETO = "Boleto"; -const PAYMENT_METHOD_CARD = "Cartão de Crédito"; const TRANSACTION_TYPE_EXPENSE = "Despesa"; const TRANSACTION_TYPE_INCOME = "Receita"; const CONDITION_RECURRING = "Recorrente"; @@ -79,7 +78,6 @@ type DashboardCurrentPeriodOverview = { installmentExpensesData: InstallmentExpensesData; topEstablishmentsData: TopEstablishmentsData; topExpensesAll: TopExpensesData; - topExpensesCardOnly: TopExpensesData; purchasesByCategoryData: PurchasesByCategoryData; }; @@ -99,7 +97,6 @@ const emptyOverview = (): DashboardCurrentPeriodOverview => ({ installmentExpensesData: { expenses: [] }, topEstablishmentsData: { establishments: [] }, topExpensesAll: { expenses: [] }, - topExpensesCardOnly: { expenses: [] }, purchasesByCategoryData: { categories: [], transactionsByCategory: {}, @@ -190,6 +187,7 @@ const buildBillsSnapshot = ( : null, isSettled: Boolean(row.isSettled), accountId: row.accountId ?? null, + transactionType: row.transactionType, })) .sort((a, b) => { if (a.isSettled !== b.isSettled) { @@ -432,14 +430,12 @@ const mapTopExpense = (row: CurrentPeriodTransactionRow): TopExpense => ({ const buildTopExpensesData = ( rows: CurrentPeriodTransactionRow[], - cardOnly: boolean, ): TopExpensesData => ({ expenses: rows .filter( (row) => row.transactionType === TRANSACTION_TYPE_EXPENSE && - shouldIncludeWithoutAutoGenerated(row.note) && - (!cardOnly || row.paymentMethod === PAYMENT_METHOD_CARD), + shouldIncludeWithoutAutoGenerated(row.note), ) .sort((a, b) => toNumber(a.amount) - toNumber(b.amount)) .slice(0, 10) @@ -617,8 +613,7 @@ export async function fetchDashboardCurrentPeriodOverview( recurringExpensesData: buildRecurringExpensesData(rows), installmentExpensesData: buildInstallmentExpensesData(rows), topEstablishmentsData: buildTopEstablishmentsData(rows), - topExpensesAll: buildTopExpensesData(rows, false), - topExpensesCardOnly: buildTopExpensesData(rows, true), + topExpensesAll: buildTopExpensesData(rows), purchasesByCategoryData: buildPurchasesByCategoryData(rows), }; } diff --git a/src/features/dashboard/widget-registry/widget-config.tsx b/src/features/dashboard/widget-registry/widget-config.tsx index 80297f0..a169fdf 100644 --- a/src/features/dashboard/widget-registry/widget-config.tsx +++ b/src/features/dashboard/widget-registry/widget-config.tsx @@ -69,8 +69,8 @@ export type WidgetConfig = { export const widgetsConfig: WidgetConfig[] = [ { id: "my-accounts", - title: "Minhas Contas", - subtitle: "Saldo consolidado disponível", + title: "Minhas contas", + subtitle: "Saldo atualizado das contas ativas", icon: , component: ({ data, @@ -114,7 +114,7 @@ export const widgetsConfig: WidgetConfig[] = [ }, { id: "payment-status", - title: "Status de Pagamento", + title: "Status de pagamento", subtitle: "Valores confirmados e pendentes", icon: , component: ({ data }) => ( @@ -144,8 +144,8 @@ export const widgetsConfig: WidgetConfig[] = [ }, { id: "income-expense-balance", - title: "Receita, Despesa e Balanço", - subtitle: "Últimos 6 meses", + title: "Receita, despesa e balanço", + subtitle: "Últimos 6 meses até o período selecionado", icon: , component: ({ data }) => ( @@ -153,8 +153,8 @@ export const widgetsConfig: WidgetConfig[] = [ }, { id: "goals-progress", - title: "Progresso de Orçamentos", - subtitle: "Orçamentos por categoria no período", + title: "Progresso de orçamentos", + subtitle: "Categorias mais próximas do limite", icon: , component: ({ data }) => ( @@ -171,7 +171,7 @@ export const widgetsConfig: WidgetConfig[] = [ }, { id: "category-trends", - title: "Tendências de Categorias", + title: "Tendências de categorias", subtitle: "Top 10 maiores variações vs. mês anterior", icon: , component: ({ data }) => ( @@ -182,21 +182,20 @@ export const widgetsConfig: WidgetConfig[] = [ }, { id: "spending-overview", - title: "Panorama de Gastos", + title: "Panorama de gastos", subtitle: "Principais despesas e frequência por local", icon: , component: ({ data }) => ( ), }, { id: "payment-overview", - title: "Comportamento de Pagamento", - subtitle: "Despesas por condição e forma de pagamento", + title: "Distribuição de despesas", + subtitle: "Por condição e forma de pagamento", icon: , component: ({ data, period, adminPayerSlug }) => ( , component: ({ data, period }) => ( , component: ({ data, period }) => ( , component: ({ data }) => ( @@ -242,8 +241,8 @@ export const widgetsConfig: WidgetConfig[] = [ }, { id: "recurring-expenses", - title: "Lançamentos Recorrentes", - subtitle: "Despesas recorrentes do período", + title: "Despesas recorrentes", + subtitle: "Despesas recorrentes deste mês", icon: , component: ({ data }) => ( @@ -251,8 +250,8 @@ export const widgetsConfig: WidgetConfig[] = [ }, { id: "installment-expenses", - title: "Lançamentos Parcelados", - subtitle: "Acompanhe as parcelas abertas", + title: "Despesas parceladas", + subtitle: "Parcelamentos mais próximos da quitação", icon: , component: ({ data }) => ( @@ -261,7 +260,7 @@ export const widgetsConfig: WidgetConfig[] = [ { id: "pagadores", title: "Pessoas", - subtitle: "Despesas por pessoa no período", + subtitle: "Maiores despesas por pessoa no período", icon: , component: ({ data }) => ( @@ -279,7 +278,7 @@ export const widgetsConfig: WidgetConfig[] = [ { id: "notes", title: "Anotações", - subtitle: "Últimas anotações ativas", + subtitle: "Anotações ativas adicionadas recentemente", icon: , component: ({ data }) => , action: (