diff --git a/public/images/dashboard-preview-dark.webp b/public/images/dashboard-preview-dark.webp index 6f8d164..9f4c815 100644 Binary files a/public/images/dashboard-preview-dark.webp and b/public/images/dashboard-preview-dark.webp differ diff --git a/public/images/dashboard-preview-light.webp b/public/images/dashboard-preview-light.webp index 6852577..0db7d02 100644 Binary files a/public/images/dashboard-preview-light.webp and b/public/images/dashboard-preview-light.webp differ diff --git a/public/images/preview-calendario-dark.webp b/public/images/preview-calendario-dark.webp index e9a8cf6..33f154a 100644 Binary files a/public/images/preview-calendario-dark.webp and b/public/images/preview-calendario-dark.webp differ diff --git a/public/images/preview-calendario-light.webp b/public/images/preview-calendario-light.webp index 72d3175..26f3bc6 100644 Binary files a/public/images/preview-calendario-light.webp and b/public/images/preview-calendario-light.webp differ diff --git a/public/images/preview-cartao-dark.webp b/public/images/preview-cartao-dark.webp index a0dd8c9..89cfeb3 100644 Binary files a/public/images/preview-cartao-dark.webp and b/public/images/preview-cartao-dark.webp differ diff --git a/public/images/preview-cartao-light.webp b/public/images/preview-cartao-light.webp index 7f2af73..8fe3fe7 100644 Binary files a/public/images/preview-cartao-light.webp and b/public/images/preview-cartao-light.webp differ diff --git a/public/images/preview-importacao-dark.webp b/public/images/preview-importacao-dark.webp index 70d8b16..afa69bf 100644 Binary files a/public/images/preview-importacao-dark.webp and b/public/images/preview-importacao-dark.webp differ diff --git a/public/images/preview-importacao-light.webp b/public/images/preview-importacao-light.webp index 2d9c07f..bc3275c 100644 Binary files a/public/images/preview-importacao-light.webp and b/public/images/preview-importacao-light.webp differ diff --git a/public/images/preview-lancamentos-dark.webp b/public/images/preview-lancamentos-dark.webp index 6c7f018..bbbc9f7 100644 Binary files a/public/images/preview-lancamentos-dark.webp and b/public/images/preview-lancamentos-dark.webp differ diff --git a/public/images/preview-lancamentos-light.webp b/public/images/preview-lancamentos-light.webp index 209d872..1cd482f 100644 Binary files a/public/images/preview-lancamentos-light.webp and b/public/images/preview-lancamentos-light.webp differ diff --git a/public/images/preview-orcamentos-dark.webp b/public/images/preview-orcamentos-dark.webp index ef5d1ed..ceefa0f 100644 Binary files a/public/images/preview-orcamentos-dark.webp and b/public/images/preview-orcamentos-dark.webp differ diff --git a/public/images/preview-orcamentos-light.webp b/public/images/preview-orcamentos-light.webp index 3a8a75b..f4965f3 100644 Binary files a/public/images/preview-orcamentos-light.webp and b/public/images/preview-orcamentos-light.webp differ diff --git a/public/images/preview-parcelas-dark.webp b/public/images/preview-parcelas-dark.webp index 4298c46..2bccba6 100644 Binary files a/public/images/preview-parcelas-dark.webp and b/public/images/preview-parcelas-dark.webp differ diff --git a/public/images/preview-parcelas-light.webp b/public/images/preview-parcelas-light.webp index 0dec711..de0b6d6 100644 Binary files a/public/images/preview-parcelas-light.webp and b/public/images/preview-parcelas-light.webp differ diff --git a/public/images/preview-pre-lancamentos-dark.webp b/public/images/preview-pre-lancamentos-dark.webp index ff3a772..70c2d63 100644 Binary files a/public/images/preview-pre-lancamentos-dark.webp and b/public/images/preview-pre-lancamentos-dark.webp differ diff --git a/public/images/preview-pre-lancamentos-light.webp b/public/images/preview-pre-lancamentos-light.webp index a1cb65b..edb98ea 100644 Binary files a/public/images/preview-pre-lancamentos-light.webp and b/public/images/preview-pre-lancamentos-light.webp differ diff --git a/src/app/(dashboard)/budgets/page.tsx b/src/app/(dashboard)/budgets/page.tsx index 71ae1de..fff6b01 100644 --- a/src/app/(dashboard)/budgets/page.tsx +++ b/src/app/(dashboard)/budgets/page.tsx @@ -20,22 +20,13 @@ const getSingleParam = ( return Array.isArray(value) ? (value[0] ?? null) : value; }; -const capitalize = (value: string) => - value.length === 0 ? value : value[0]?.toUpperCase() + value.slice(1); - export default async function Page({ searchParams }: PageProps) { await connection(); const userId = await getUserId(); const resolvedSearchParams = searchParams ? await searchParams : undefined; const periodoParam = getSingleParam(resolvedSearchParams, "periodo"); - const { - period: selectedPeriod, - monthName: rawMonthName, - year, - } = parsePeriodParam(periodoParam); - - const periodLabel = `${capitalize(rawMonthName)} ${year}`; + const { period: selectedPeriod } = parsePeriodParam(periodoParam); const { budgets, categoriesOptions } = await fetchBudgetsForUser( userId, @@ -49,7 +40,6 @@ export default async function Page({ searchParams }: PageProps) { budgets={budgets} categories={categoriesOptions} selectedPeriod={selectedPeriod} - periodLabel={periodLabel} /> ); diff --git a/src/app/(dashboard)/categories/history/page.tsx b/src/app/(dashboard)/categories/history/page.tsx index 2d165f0..126fc1f 100644 --- a/src/app/(dashboard)/categories/history/page.tsx +++ b/src/app/(dashboard)/categories/history/page.tsx @@ -1,6 +1,6 @@ import { connection } from "next/server"; import { fetchCategoryHistory } from "@/features/dashboard/categories/category-history-queries"; -import { CategoryHistoryWidget } from "@/features/dashboard/components/category-history-widget"; +import { CategoryHistoryWidget } from "@/features/dashboard/components/widgets/category-history-widget"; import { getUser } from "@/shared/lib/auth/server"; import { getCurrentPeriod } from "@/shared/utils/period"; diff --git a/src/app/(dashboard)/reports/category-trends/page.tsx b/src/app/(dashboard)/reports/category-trends/page.tsx index f7ed25a..5ed6964 100644 --- a/src/app/(dashboard)/reports/category-trends/page.tsx +++ b/src/app/(dashboard)/reports/category-trends/page.tsx @@ -40,7 +40,9 @@ export default async function Page({ searchParams }: PageProps) { // Extract query params const inicioParam = getSingleParam(resolvedSearchParams, "inicio"); const fimParam = getSingleParam(resolvedSearchParams, "fim"); - const categoriasParam = getSingleParam(resolvedSearchParams, "categories"); + const categoriasParam = + getSingleParam(resolvedSearchParams, "categorias") ?? + getSingleParam(resolvedSearchParams, "categories"); // Calculate default period (last 6 months) const currentPeriod = getCurrentPeriod(); diff --git a/src/app/(landing-page)/page.tsx b/src/app/(landing-page)/page.tsx index 5e55bf2..d1e783a 100644 --- a/src/app/(landing-page)/page.tsx +++ b/src/app/(landing-page)/page.tsx @@ -30,7 +30,6 @@ import { NavbarShell } from "@/shared/components/navigation/navbar/navbar-shell" import { Badge } from "@/shared/components/ui/badge"; import { Button } from "@/shared/components/ui/button"; import { Card, CardContent } from "@/shared/components/ui/card"; -import { DotPattern } from "@/shared/components/ui/dot-pattern"; import { getOptionalUserSession } from "@/shared/lib/auth/server"; export default async function Page() { @@ -57,7 +56,7 @@ export default async function Page() { {label} @@ -70,9 +69,9 @@ export default async function Page() { (session?.user ? ( @@ -83,7 +82,7 @@ export default async function Page() { @@ -91,7 +90,7 @@ export default async function Page() { @@ -107,18 +106,6 @@ export default async function Page() { {/* Hero Section */}
-
- -
-
-
@@ -265,72 +252,34 @@ export default async function Page() { -
- {mainFeatures.map((feature) => ( +
+ {[...mainFeatures, ...extraFeatures].map((feature) => ( - -
+ +
-
-

- {feature.title} -

-

- {feature.description} -

-
+

+ {feature.title} +

+

+ {feature.description} +

))}
- - -
-

- Também inclui -

-
- {extraFeatures.map((feature) => ( -
-
- -
-
-

- {feature.title} -

-

- {feature.description} -

-
-
- ))} -
-
-
@@ -396,14 +345,14 @@ export default async function Page() { {pwaHighlights.map((item) => (
  • @@ -438,17 +387,19 @@ export default async function Page() { pré-lançamentos automaticamente para você revisar na inbox.

      - {companionSteps.map((step, index) => ( + {companionSteps.map((step) => (
    1. - - {index + 1} - + +

      {step.title} @@ -545,14 +496,14 @@ export default async function Page() {

      @@ -633,14 +584,14 @@ export default async function Page() {
      diff --git a/src/app/globals.css b/src/app/globals.css index 4724d60..282146f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -10,7 +10,7 @@ :root { --background: oklch(97.412% 0.00332 67.032); --foreground: oklch(27% 0.008 45); - --card: oklch(99% 0.002 67); + --card: oklch(100% 0 0); --card-foreground: var(--foreground); --popover: oklch(100% 0 0); --popover-foreground: var(--foreground); @@ -36,7 +36,7 @@ --destructive: oklch(55% 0.22 27); --destructive-foreground: oklch(98% 0.005 30); - --border: oklch(90.274% 0.01362 60.342); + --border: oklch(92.323% 0.01276 63.703); --input: var(--border); --ring: var(--primary); @@ -57,10 +57,6 @@ --data-4: oklch(74% 0.18 55); /* âmbar */ --data-5: oklch(78% 0.16 68); /* âmbar-dourado */ --data-6: oklch(76% 0.15 82); /* amarelo-quente */ - --data-7: oklch(70% 0.17 95); /* amarelo-lima */ - --data-8: oklch(65% 0.18 108); /* lima-verde */ - --data-9: oklch(62% 0.17 120); /* verde-oliva claro */ - --data-10: oklch(56% 0.15 10); /* terracota escuro */ --sidebar: oklch(99.3% 0.0015 75); --sidebar-foreground: var(--foreground); @@ -71,7 +67,7 @@ --sidebar-border: oklch(91% 0.004 70); --sidebar-ring: var(--primary); - --radius: 0.625rem; + --radius: 0.7rem; --shadow-2xs: 0 1px 2px 0px oklch(35% 0.02 45 / 0.04); --shadow-xs: 0 1px 3px 0px oklch(35% 0.02 45 / 0.06); @@ -94,7 +90,7 @@ .dark { --background: oklch(18% 0.004 55); --foreground: oklch(93% 0.008 80); - --card: oklch(21.5% 0.004 55); + --card: oklch(21.531% 0.00369 48.293); --card-foreground: var(--foreground); --popover: oklch(24% 0.004 55); --popover-foreground: var(--foreground); @@ -120,7 +116,7 @@ --destructive: oklch(62% 0.2 28); --destructive-foreground: oklch(98% 0.005 30); - --border: oklch(31% 0.004 55); + --border: oklch(28% 0.0035 55); --input: var(--border); --ring: var(--primary); @@ -141,10 +137,6 @@ --data-4: oklch(81% 0.18 55); --data-5: oklch(84% 0.16 68); --data-6: oklch(82% 0.15 82); - --data-7: oklch(77% 0.17 95); - --data-8: oklch(72% 0.18 108); - --data-9: oklch(69% 0.17 120); - --data-10: oklch(63% 0.15 10); --sidebar: oklch(15.5% 0.004 55); --sidebar-foreground: var(--foreground); @@ -155,7 +147,7 @@ --sidebar-border: oklch(30% 0.004 55); --sidebar-ring: var(--primary); - --radius: 0.625rem; + --radius: 0.7rem; --shadow-2xs: 0 1px 2px 0px oklch(0% 0 0 / 0.3); --shadow-xs: 0 1px 3px 0px oklch(0% 0 0 / 0.4); diff --git a/src/features/accounts/components/account-card.tsx b/src/features/accounts/components/account-card.tsx index d323159..26af83b 100644 --- a/src/features/accounts/components/account-card.tsx +++ b/src/features/accounts/components/account-card.tsx @@ -1,4 +1,5 @@ "use client"; + import { RiArrowLeftRightLine, RiDeleteBin5Line, @@ -47,6 +48,13 @@ export function AccountCard({ }: AccountCardProps) { const isInactive = status?.toLowerCase() === "inativa"; + const balanceColor = + balance > 0 + ? "text-success" + : balance < 0 + ? "text-destructive" + : "text-foreground"; + const actions = [ { label: "editar", @@ -75,78 +83,90 @@ export function AccountCard({ ].filter((action) => typeof action.onClick === "function"); return ( - - -
      - {icon ? ( -
      - {icon} + +
      +
      +
      + {icon} +
      +
      +
      +

      + {accountName} +

      + {excludeFromBalance || excludeInitialBalanceFromIncome ? ( + + + + + +
      + {excludeFromBalance && ( +

      + Desconsiderado do saldo total: Esta + conta não é incluída no cálculo do saldo total geral. +

      + )} + {excludeInitialBalanceFromIncome && ( +

      + + Saldo inicial desconsiderado das receitas: + {" "} + O saldo inicial desta conta não é contabilizado como + receita nas métricas. +

      + )} +
      +
      +
      + ) : null}
      - ) : null} -

      - {accountName} -

      - {(excludeFromBalance || excludeInitialBalanceFromIncome) && ( - - -
      - -
      -
      - -
      - {excludeFromBalance && ( -

      - Desconsiderado do saldo total: Esta conta - não é incluída no cálculo do saldo total geral. -

      - )} - {excludeInitialBalanceFromIncome && ( -

      - - Saldo inicial desconsiderado das receitas: - {" "} - O saldo inicial desta conta não é contabilizado como - receita nas métricas. -

      - )} -
      -
      -
      - )} +

      {status}

      +
      -
      - -

      {accountType}

      +

      {accountType}

      +
      + + +
      + Saldo +
      - {actions.length > 0 ? ( - - {actions.map(({ label, icon, onClick, variant }) => ( - - ))} - - ) : null} + + {actions.map(({ label, icon, onClick, variant }) => ( + + ))} + ); } diff --git a/src/features/accounts/components/account-statement-card.tsx b/src/features/accounts/components/account-statement-card.tsx index 750a8db..fe38f53 100644 --- a/src/features/accounts/components/account-statement-card.tsx +++ b/src/features/accounts/components/account-statement-card.tsx @@ -86,7 +86,7 @@ export function AccountStatementCard({

      -
      -
      - -
      -
      - - -
      - {children} -
      + + +
      {children}
      diff --git a/src/features/budgets/components/budget-card.tsx b/src/features/budgets/components/budget-card.tsx index be51b42..2e56ad4 100644 --- a/src/features/budgets/components/budget-card.tsx +++ b/src/features/budgets/components/budget-card.tsx @@ -15,7 +15,6 @@ import type { Budget } from "./types"; interface BudgetCardProps { budget: Budget; - periodLabel: string; onEdit: (budget: Budget) => void; onRemove: (budget: Budget) => void; } @@ -29,81 +28,88 @@ const buildUsagePercent = (spent: number, limit: number) => { }; const formatCategoryName = (budget: Budget) => - budget.category?.name ?? "Category removida"; + budget.category?.name ?? "Categoria removida"; -export function BudgetCard({ - budget, - periodLabel, - onEdit, - onRemove, -}: BudgetCardProps) { +export function BudgetCard({ budget, onEdit, onRemove }: BudgetCardProps) { const { amount: limit, spent } = budget; const exceeded = spent > limit && limit >= 0; const difference = Math.abs(spent - limit); const usagePercent = buildUsagePercent(spent, limit); + const remaining = Math.max(limit - spent, 0); return ( - - -
      - +
      + +
      +

      + {formatCategoryName(budget)} +

      +
      +
      + + +
      + + {exceeded ? "Excedido em" : "Disponível"} + + -
      -

      - {formatCategoryName(budget)} -

      -

      - Orçamento de {periodLabel} -

      -
      -
      -
      - Gasto até agora +
      +
      + Orçamento
      - -
      - Limite - -
      - -
      - {exceeded ? ( -
      - Excedeu em -
      - ) : ( -
      - Restam {" "} - disponíveis. -
      - )} +
      + Gasto +
      + +
      + + + {usagePercent.toFixed(1)}% utilizado + +
      - + + {budget.category && ( detalhes @@ -111,7 +117,7 @@ export function BudgetCard({ diff --git a/src/features/budgets/components/budgets-page.tsx b/src/features/budgets/components/budgets-page.tsx index e5d45fd..5c4f572 100644 --- a/src/features/budgets/components/budgets-page.tsx +++ b/src/features/budgets/components/budgets-page.tsx @@ -19,14 +19,12 @@ interface BudgetsPageProps { budgets: Budget[]; categories: BudgetCategory[]; selectedPeriod: string; - periodLabel: string; } export function BudgetsPage({ budgets, categories, selectedPeriod, - periodLabel, }: BudgetsPageProps) { const [editOpen, setEditOpen] = useState(false); const [selectedBudget, setSelectedBudget] = useState(null); @@ -137,7 +135,6 @@ export function BudgetsPage({ diff --git a/src/features/budgets/queries.ts b/src/features/budgets/queries.ts index b350018..2d3e902 100644 --- a/src/features/budgets/queries.ts +++ b/src/features/budgets/queries.ts @@ -13,7 +13,7 @@ const toNumber = (value: string | number | null | undefined) => { return 0; }; -export type BudgetData = { +type BudgetData = { id: string; amount: number; spent: number; diff --git a/src/features/calendar/components/calendar-grid.tsx b/src/features/calendar/components/calendar-grid.tsx index 638510f..ede1eee 100644 --- a/src/features/calendar/components/calendar-grid.tsx +++ b/src/features/calendar/components/calendar-grid.tsx @@ -1,10 +1,8 @@ "use client"; import { DayCell } from "@/features/calendar/components/day-cell"; - import type { CalendarDay } from "@/shared/lib/types/calendar"; import { WEEK_DAYS_SHORT } from "@/shared/utils/calendar"; -import { cn } from "@/shared/utils/ui"; type CalendarGridProps = { days: CalendarDay[]; @@ -18,21 +16,18 @@ export function CalendarGrid({ onCreateDay, }: CalendarGridProps) { return ( -
      +
      {WEEK_DAYS_SHORT.map((dayName) => ( - + {dayName} ))}
      -
      +
      {days.map((day) => ( -
      +
      ))} diff --git a/src/features/calendar/components/calendar-legend.tsx b/src/features/calendar/components/calendar-legend.tsx index dcc763d..81f46eb 100644 --- a/src/features/calendar/components/calendar-legend.tsx +++ b/src/features/calendar/components/calendar-legend.tsx @@ -1,34 +1,32 @@ "use client"; import { EVENT_TYPE_STYLES } from "@/features/calendar/components/day-cell"; -import StatusDot from "@/shared/components/status-dot"; -import { Card } from "@/shared/components/ui/card"; -import type { CalendarEvent } from "@/shared/lib/types/calendar"; +import { cn } from "@/shared/utils/ui"; -const LEGEND_ITEMS: Array<{ - type?: CalendarEvent["type"]; - label: string; - dotColor?: string; -}> = [ - { type: "transaction", label: "Lançamentos" }, - { type: "boleto", label: "Boleto com vencimento" }, - { type: "card", label: "Vencimento de cartão" }, - { label: "Pagamento fatura", dotColor: "bg-success" }, +const LEGEND_ITEMS = [ + { label: "Lançamentos", ...EVENT_TYPE_STYLES.transaction }, + { label: "Boletos", ...EVENT_TYPE_STYLES.boleto }, + { label: "Fatura de Cartão", ...EVENT_TYPE_STYLES.card }, ]; export function CalendarLegend() { return ( - - {LEGEND_ITEMS.map((item, index) => { - const dotColor = - item.dotColor || (item.type ? EVENT_TYPE_STYLES[item.type].dot : ""); - return ( - - - {item.label} - - ); - })} - +
        + {LEGEND_ITEMS.map((item) => ( +
      • + + {item.label} +
      • + ))} +
      ); } diff --git a/src/features/calendar/components/day-cell.tsx b/src/features/calendar/components/day-cell.tsx index eae9b3f..0e3f386 100644 --- a/src/features/calendar/components/day-cell.tsx +++ b/src/features/calendar/components/day-cell.tsx @@ -1,6 +1,6 @@ "use client"; -import { RiAddLine } from "@remixicon/react"; +import { RiAddLine, RiCheckboxCircleFill } from "@remixicon/react"; import type { KeyboardEvent, MouseEvent } from "react"; import type { CalendarDay, CalendarEvent } from "@/shared/lib/types/calendar"; import { currencyFormatter } from "@/shared/utils/currency"; @@ -14,44 +14,33 @@ type DayCellProps = { export const EVENT_TYPE_STYLES: Record< CalendarEvent["type"], - { wrapper: string; dot: string; accent?: string } + { wrapper: string; dot: string } > = { transaction: { - wrapper: - "bg-warning/10 text-warning dark:bg-warning/5 dark:text-warning border-l-4 border-warning", - dot: "bg-warning", + wrapper: "bg-primary/10 text-primary dark:bg-primary/5 dark:text-primary", + dot: "bg-primary", }, boleto: { - wrapper: - "bg-info/10 text-info dark:bg-info/5 dark:text-info border-l-4 border-info", + wrapper: "bg-info/10 text-info dark:bg-info/5 dark:text-info", dot: "bg-info", }, card: { wrapper: - "bg-violet-100 text-violet-600 dark:bg-violet-900/10 dark:text-violet-50 border-l-4 border-violet-500", - dot: "bg-violet-600", + "bg-violet-100 text-violet-600 dark:bg-violet-900/10 dark:text-violet-500", + dot: "bg-violet-600 dark:bg-violet-500", }, }; -const eventStyles = EVENT_TYPE_STYLES; - const formatCurrencyValue = (value: number | null | undefined) => currencyFormatter.format(Math.abs(value ?? 0)); -const formatAmount = (event: Extract) => - formatCurrencyValue(event.transaction.amount); - const buildEventLabel = (event: CalendarEvent) => { switch (event.type) { - case "transaction": { + case "transaction": + case "boleto": return event.transaction.name; - } - case "boleto": { - return event.transaction.name; - } - case "card": { + case "card": return event.card.name; - } default: return ""; } @@ -59,60 +48,48 @@ const buildEventLabel = (event: CalendarEvent) => { const buildEventComplement = (event: CalendarEvent) => { switch (event.type) { - case "transaction": { - return formatAmount(event); - } - case "boleto": { + case "transaction": + case "boleto": return formatCurrencyValue(event.transaction.amount); - } - case "card": { - if (event.card.totalDue !== null) { - return formatCurrencyValue(event.card.totalDue); - } - return null; - } + case "card": + return event.card.totalDue !== null + ? formatCurrencyValue(event.card.totalDue) + : null; default: return null; } }; -const isPagamentoFatura = (event: CalendarEvent) => { - return ( - event.type === "transaction" && - event.transaction.name.startsWith("Pagamento fatura -") - ); -}; - -const getEventStyle = (event: CalendarEvent) => { - if (isPagamentoFatura(event)) { - return { - wrapper: - "bg-success/10 text-success dark:bg-success/5 dark:text-success border-l-4 border-success", - dot: "bg-success", - }; - } - return eventStyles[event.type]; +const isPaid = (event: CalendarEvent) => { + if (event.type === "boleto") return Boolean(event.transaction.isSettled); + if (event.type === "card") return event.card.isPaid; + return false; }; const DayEventPreview = ({ event }: { event: CalendarEvent }) => { const complement = buildEventComplement(event); const label = buildEventLabel(event); - const style = getEventStyle(event); + const style = EVENT_TYPE_STYLES[event.type]; return (
      + {label} + {isPaid(event) && ( + + )}
      {complement ? ( - - {complement} - + {complement} ) : null}
      ); @@ -143,8 +120,8 @@ export function DayCell({ day, onSelect, onCreate }: DayCellProps) { onClick={() => onSelect(day)} onKeyDown={handleKeyDown} className={cn( - "group flex h-full cursor-pointer flex-col gap-1.5 rounded-lg border border-transparent bg-card/80 p-2 text-left transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:border-primary/40 hover:bg-primary/5 dark:hover:bg-accent", - !day.isCurrentMonth && "opacity-60", + "group flex h-full cursor-pointer flex-col gap-1.5 rounded-lg border bg-card/80 p-2 text-left transition-all duration-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 hover:border-primary/40 hover:bg-primary/5 dark:hover:bg-accent", + !day.isCurrentMonth && "bg-muted/20 opacity-60", day.isToday && "border-primary/70 bg-primary/5 hover:border-primary", )} > @@ -159,14 +136,16 @@ export function DayCell({ day, onSelect, onCreate }: DayCellProps) { > {day.label} - + {day.isCurrentMonth && ( + + )}
      diff --git a/src/features/calendar/components/event-modal.tsx b/src/features/calendar/components/event-modal.tsx index e44657f..ae9229d 100644 --- a/src/features/calendar/components/event-modal.tsx +++ b/src/features/calendar/components/event-modal.tsx @@ -1,5 +1,6 @@ "use client"; +import { RiCalendarEventLine } from "@remixicon/react"; import type { ReactNode } from "react"; import { EVENT_TYPE_STYLES } from "@/features/calendar/components/day-cell"; import MoneyValues from "@/shared/components/money-values"; @@ -29,17 +30,13 @@ type EventModalProps = { const EventCard = ({ children, type, - isPagamentoFatura = false, }: { children: ReactNode; type: CalendarEvent["type"]; - isPagamentoFatura?: boolean; }) => { - const style = isPagamentoFatura - ? { dot: "bg-success" } - : EVENT_TYPE_STYLES[type]; + const style = EVENT_TYPE_STYLES[type]; return ( - + , ) => { const isReceita = event.transaction.transactionType === "Receita"; - const isPagamentoFatura = - event.transaction.name.startsWith("Pagamento fatura -"); return ( - +
      - + {event.transaction.name} - -
      - {event.transaction.categoriaName} -
      + {event.transaction.categoriaName}
      - - - + amount={event.transaction.amount} + />
      ); @@ -91,59 +81,80 @@ const renderLancamento = ( const renderBoleto = (event: Extract) => { const isPaid = Boolean(event.transaction.isSettled); - const dueDate = event.transaction.dueDate; - const dueDateLabel = formatFinancialDateLabel(dueDate, "Vence em", { - day: "2-digit", - month: "2-digit", - year: "numeric", - }); + const dueDateLabel = formatFinancialDateLabel( + event.transaction.dueDate, + "Vence em", + DATE_FORMAT, + ); + const paymentDateLabel = isPaid + ? formatFinancialDateLabel( + event.transaction.boletoPaymentDate, + "Pago em", + DATE_FORMAT, + ) + : null; return (
      -
      - - {event.transaction.name} - - + + {event.transaction.name} + +
      {dueDateLabel && ( - - {dueDateLabel} - + {dueDateLabel} + )} + {paymentDateLabel && ( + {paymentDateLabel} )}
      - - {isPaid ? "Pago" : "Pendente"} + {isPaid ? "Pago" : "Pendente"}
      - - - +
      ); }; -const renderCard = (event: Extract) => ( - -
      -
      -
      - - Vencimento Fatura - {event.card.name} - -
      +const renderCard = (event: Extract) => { + const paymentDateLabel = event.card.isPaid + ? formatFinancialDateLabel(event.card.paymentDate, "Pago em", DATE_FORMAT) + : null; - {event.card.status ?? "Invoice"} + return ( + +
      +
      + + Vencimento Fatura — {event.card.name} + + {paymentDateLabel && ( + {paymentDateLabel} + )} + + {event.card.isPaid ? "Pago" : (event.card.status ?? "Fatura")} + +
      + {event.card.totalDue !== null ? ( + + ) : null}
      - {event.card.totalDue !== null ? ( - - - - ) : null} -
      - -); + + ); +}; + +const SECTION_LABELS: Record = { + transaction: "Lançamentos", + boleto: "Boletos", + card: "Faturas", +}; const renderEvent = (event: CalendarEvent) => { switch (event.type) { @@ -169,28 +180,50 @@ export function EventModal({ open, day, onClose, onCreate }: EventModalProps) { onCreate(day.date); }; - const description = day?.events.length - ? "Confira os lançamentos e vencimentos cadastrados para este dia." - : "Nenhum lançamento encontrado para este dia. Você pode criar um novo lançamento agora."; + const hasEvents = Boolean(day?.events.length); + + const grouped = day + ? { + transaction: day.events.filter((e) => e.type === "transaction"), + boleto: day.events.filter((e) => e.type === "boleto"), + card: day.events.filter((e) => e.type === "card"), + } + : null; return ( (!value ? onClose() : null)}> {formattedDate} - {description} + + {hasEvents + ? "Lançamentos e vencimentos cadastrados para este dia." + : "Nenhum lançamento encontrado para este dia."} + -
      - {day?.events.length ? ( - day.events.map((event) => ( -
      {renderEvent(event)}
      - )) +
      + {hasEvents && grouped ? ( + (["transaction", "boleto", "card"] as const) + .filter((type) => grouped[type].length > 0) + .map((type) => ( +
      +

      + {SECTION_LABELS[type]} +

      +
      + {grouped[type].map((event) => ( +
      {renderEvent(event)}
      + ))} +
      +
      + )) ) : ( -
      - Nenhum lançamento ou vencimento registrado. Clique em{" "} - Novo lançamento{" "} - para começar. +
      + +

      + Nenhum lançamento registrado para este dia. +

      )}
      diff --git a/src/features/calendar/queries.ts b/src/features/calendar/queries.ts index 02f831a..5200f42 100644 --- a/src/features/calendar/queries.ts +++ b/src/features/calendar/queries.ts @@ -17,6 +17,7 @@ import { parsePeriod } from "@/shared/utils/period"; const PAYMENT_METHOD_BOLETO = "Boleto"; const TRANSACTION_TYPE_TRANSFERENCIA = "Transferência"; +const PAYMENT_PREFIX = "Pagamento fatura - "; const clampDayInMonth = (year: number, monthIndex: number, day: number) => { const lastDay = new Date(Date.UTC(year, monthIndex + 1, 0)).getUTCDate(); @@ -88,19 +89,28 @@ export const fetchCalendarData = async ({ const transactionData = mapTransactionsData(transactionRows); const events: CalendarEvent[] = []; + // Totais por cartão para exibir no vencimento const cardTotals = new Map(); for (const item of transactionData) { - if (!item.cardId || item.period !== period) { - continue; - } + if (!item.cardId || item.period !== period) continue; const amount = Math.abs(item.amount ?? 0); cardTotals.set(item.cardId, (cardTotals.get(item.cardId) ?? 0) + amount); } + // Pagamentos de fatura por nome do cartão → data de pagamento + const paymentByCardName = new Map(); for (const item of transactionData) { + if (!item.name.startsWith(PAYMENT_PREFIX)) continue; + const cardName = item.name.slice(PAYMENT_PREFIX.length); + paymentByCardName.set(cardName, item.purchaseDate?.slice(0, 10) ?? null); + } + + for (const item of transactionData) { + // Pagamentos de fatura são consumidos pelos eventos de cartão + if (item.name.startsWith(PAYMENT_PREFIX)) continue; + const isBoleto = item.paymentMethod === PAYMENT_METHOD_BOLETO; - // Para boletos, exibir apenas na data de vencimento if (isBoleto) { if ( item.dueDate && @@ -114,7 +124,6 @@ export const fetchCalendarData = async ({ }); } } else { - // Para outros tipos de lançamento, exibir na data de compra const purchaseDateKey = item.purchaseDate.slice(0, 10); if (isWithinRange(purchaseDateKey, rangeStartKey, rangeEndKey)) { events.push({ @@ -127,22 +136,21 @@ export const fetchCalendarData = async ({ } } - // Exibir vencimentos apenas de cartões com lançamentos do período + // Vencimentos de cartões com lançamentos no período for (const card of cardRows) { - if (!cardTotals.has(card.id)) { - continue; - } + if (!cardTotals.has(card.id)) continue; const dueDayNumber = Number.parseInt(card.dueDay ?? "", 10); - if (Number.isNaN(dueDayNumber)) { - continue; - } + if (Number.isNaN(dueDayNumber)) continue; const normalizedDay = clampDayInMonth(year, monthIndex, dueDayNumber); const dueDateKey = formatDateKey( new Date(Date.UTC(year, monthIndex, normalizedDay)), ); + const isPaid = paymentByCardName.has(card.name); + const paymentDate = paymentByCardName.get(card.name) ?? null; + events.push({ id: `${card.id}:cartao`, type: "card", @@ -156,6 +164,8 @@ export const fetchCalendarData = async ({ status: card.status, logo: card.logo ?? null, totalDue: cardTotals.get(card.id) ?? null, + isPaid, + paymentDate, }, }); } diff --git a/src/features/cards/components/card-item.tsx b/src/features/cards/components/card-item.tsx index 4b63804..230dccf 100644 --- a/src/features/cards/components/card-item.tsx +++ b/src/features/cards/components/card-item.tsx @@ -84,39 +84,11 @@ export function CardItem({ const logoPath = resolveLogoSrc(logo); const brandAsset = resolveCardBrandAsset(brand); const isInactive = status?.toLowerCase() === "inativo"; - const metrics = - limitTotal === null || used === null || available === null - ? null - : [ - { label: "Limite Total", value: limitTotal }, - { label: "Em uso", value: used }, - { label: "Disponível", value: available }, - ]; - - const actions = [ - { - label: "editar", - icon: , - onClick: onEdit, - className: "text-primary", - }, - { - label: "ver fatura", - icon: , - onClick: onInvoice, - className: "text-primary", - }, - { - label: "remover", - icon: , - onClick: onRemove, - className: "text-destructive", - }, - ]; + const hasMetrics = limitTotal !== null && used !== null && available !== null; return ( - +
      {logoPath ? ( @@ -135,8 +107,8 @@ export function CardItem({ ) : null}
      -
      -

      +
      +

      {name}

      {note ? ( @@ -166,14 +138,14 @@ export function CardItem({
      {brandAsset ? ( -
      +
      {`Bandeira @@ -185,56 +157,65 @@ export function CardItem({ )}
      -
      +
      - Fecha dia{" "} - - {formatDay(closingDay)} + Fecha em{" "} + + dia {formatDay(closingDay)} - Vence dia{" "} - - {formatDay(dueDay)} + Vence em{" "} + + dia {formatDay(dueDay)}
      - - {metrics ? ( + + {hasMetrics && + available !== null && + used !== null && + limitTotal !== null ? ( <> -
      -
      -

      - -

      - - {metrics[0].label} - -
      +
      + Disponível + +
      -
      -

      - - -

      +
      +
      - {metrics[1].label} + Limite total +
      - -
      -

      - -

      - - {metrics[2].label} - +
      + Em uso +
      - +
      + + + {usagePercent.toFixed(1)}% utilizado + +
      ) : (

      @@ -243,21 +224,31 @@ export function CardItem({ )} - - {actions.map(({ label, icon, onClick, className }) => ( - - ))} + + + + ); diff --git a/src/features/cards/queries.ts b/src/features/cards/queries.ts index d94ce9b..e528dce 100644 --- a/src/features/cards/queries.ts +++ b/src/features/cards/queries.ts @@ -3,7 +3,7 @@ import { cards, financialAccounts, transactions } from "@/db/schema"; import { db } from "@/shared/lib/db"; import { loadLogoOptions } from "@/shared/lib/logo/options"; -export type CardData = { +type CardData = { id: string; name: string; brand: string; diff --git a/src/features/categories/components/categories-page.tsx b/src/features/categories/components/categories-page.tsx index cb3726c..b580d4f 100644 --- a/src/features/categories/components/categories-page.tsx +++ b/src/features/categories/components/categories-page.tsx @@ -30,10 +30,11 @@ import { import { CATEGORY_TYPE_LABEL, CATEGORY_TYPES, + type CategoryType, } from "@/shared/lib/categories/constants"; +import { CategoryIconBadge } from "@/shared/components/entity-avatar"; import { CategoryDialog } from "./category-dialog"; -import { CategoryIconBadge } from "./category-icon-badge"; -import type { Category, CategoryType } from "./types"; +import type { Category } from "./types"; const CATEGORIAS_PROTEGIDAS = [ "Transferência interna", diff --git a/src/features/categories/components/category-detail-header.tsx b/src/features/categories/components/category-detail-header.tsx index d5ef785..8cd5181 100644 --- a/src/features/categories/components/category-detail-header.tsx +++ b/src/features/categories/components/category-detail-header.tsx @@ -1,11 +1,10 @@ -import { RiArrowDownSFill, RiArrowUpSFill } from "@remixicon/react"; +import { PercentageChangeIndicator } from "@/features/dashboard/components/percentage-change-indicator"; +import { CategoryIconBadge } from "@/shared/components/entity-avatar"; import { TransactionTypeBadge } from "@/shared/components/transaction-type-badge"; import { Card } from "@/shared/components/ui/card"; import type { CategoryType } from "@/shared/lib/categories/constants"; import { currencyFormatter } from "@/shared/utils/currency"; import { formatPercentage } from "@/shared/utils/percentage"; -import { cn } from "@/shared/utils/ui"; -import { CategoryIconBadge } from "./category-icon-badge"; type CategorySummary = { id: string; @@ -33,33 +32,6 @@ export function CategoryDetailHeader({ percentageChange, transactionCount, }: CategoryDetailHeaderProps) { - const isIncrease = - typeof percentageChange === "number" && percentageChange > 0; - const isDecrease = - typeof percentageChange === "number" && percentageChange < 0; - - const variationColor = - category.type === "receita" - ? isIncrease - ? "text-success" - : isDecrease - ? "text-destructive" - : "text-muted-foreground" - : isIncrease - ? "text-destructive" - : isDecrease - ? "text-success" - : "text-muted-foreground"; - - const variationIcon = - isIncrease || isDecrease ? ( - isIncrease ? ( - - ) : ( - - ) - ) : null; - const variationLabel = typeof percentageChange === "number" ? formatPercentage(percentageChange, { @@ -115,15 +87,13 @@ export function CategoryDetailHeader({

      Variação vs mês anterior

      -
      - {variationIcon} - {variationLabel} -
      +
      diff --git a/src/features/categories/components/category-icon-badge.tsx b/src/features/categories/components/category-icon-badge.tsx deleted file mode 100644 index eb48c79..0000000 --- a/src/features/categories/components/category-icon-badge.tsx +++ /dev/null @@ -1,6 +0,0 @@ -// Re-export from shared — componente movido para src/shared/components/entity-avatar/ -export { - CategoryIconBadge, - type CategoryIconBadgeProps, - type CategoryIconBadgeSize, -} from "@/shared/components/entity-avatar"; diff --git a/src/features/categories/components/types.ts b/src/features/categories/components/types.ts index 11db653..b242558 100644 --- a/src/features/categories/components/types.ts +++ b/src/features/categories/components/types.ts @@ -1,11 +1,5 @@ import type { CategoryType } from "@/shared/lib/categories/constants"; -export type { CategoryType } from "@/shared/lib/categories/constants"; -export { - CATEGORY_TYPE_LABEL, - CATEGORY_TYPES, -} from "@/shared/lib/categories/constants"; - export type Category = { id: string; name: string; diff --git a/src/features/categories/queries.ts b/src/features/categories/queries.ts index 1c08333..82607b1 100644 --- a/src/features/categories/queries.ts +++ b/src/features/categories/queries.ts @@ -1,6 +1,6 @@ import { eq } from "drizzle-orm"; import { type Category, categories } from "@/db/schema"; -import type { CategoryType } from "@/features/categories/components/types"; +import type { CategoryType } from "@/shared/lib/categories/constants"; import { db } from "@/shared/lib/db"; export type CategoryData = { diff --git a/src/features/inbox/components/inbox-card.tsx b/src/features/inbox/components/inbox-card.tsx index 57d93bc..7a5f27b 100644 --- a/src/features/inbox/components/inbox-card.tsx +++ b/src/features/inbox/components/inbox-card.tsx @@ -149,9 +149,9 @@ export const InboxCard = memo(function InboxCard({
      - + {item.originalTitle && ( -

      {item.originalTitle}

      +

      {item.originalTitle}

      )}

      {item.originalText} diff --git a/src/features/inbox/components/inbox-items-list.tsx b/src/features/inbox/components/inbox-items-list.tsx index 4e02e3f..0dd150d 100644 --- a/src/features/inbox/components/inbox-items-list.tsx +++ b/src/features/inbox/components/inbox-items-list.tsx @@ -102,32 +102,29 @@ export function InboxItemsList({ const groups = groupItemsByDay(items); return ( -

      - {groups.map((group) => ( -
      -
      - -

      {group.label}

      +
      + {groups.flatMap((group) => + group.items.map((item) => ( +
      +
      + + {group.label} +
      +
      -
      - {group.items.map((item) => ( - - ))} -
      -
      - ))} + )), + )}
      ); } diff --git a/src/features/invoices/components/invoice-summary-card.tsx b/src/features/invoices/components/invoice-summary-card.tsx index 9c298b7..5c967b6 100644 --- a/src/features/invoices/components/invoice-summary-card.tsx +++ b/src/features/invoices/components/invoice-summary-card.tsx @@ -191,9 +191,9 @@ export function InvoiceSummaryCard({

      Valor da fatura

      diff --git a/src/features/landing/components/setup-tabs.tsx b/src/features/landing/components/setup-tabs.tsx index 51f2273..5a3c995 100644 --- a/src/features/landing/components/setup-tabs.tsx +++ b/src/features/landing/components/setup-tabs.tsx @@ -86,6 +86,13 @@ export function SetupTabs() { ); } +const DATA_COLORS = [ + "var(--data-1)", + "var(--data-3)", + "var(--data-5)", + "var(--data-4)", +]; + function StepCard({ step, title, @@ -95,11 +102,18 @@ function StepCard({ title: string; children: React.ReactNode; }) { + const colorVar = DATA_COLORS[(step - 1) % DATA_COLORS.length]; return (
      -
      +
      {step}
      diff --git a/src/features/landing/constants.ts b/src/features/landing/constants.ts index 91dbdf7..8adea3c 100644 --- a/src/features/landing/constants.ts +++ b/src/features/landing/constants.ts @@ -49,42 +49,42 @@ export const mainFeatures: FeatureItem[] = [ icon: RiWalletLine, title: "Contas e transações", description: - "Registre suas contas bancárias, cartões e dinheiro. Adicione receitas, despesas e transferências. Organize por categorias. Extratos detalhados por conta.", - colorVar: "var(--data-9)", + "Contas bancárias, cartões e dinheiro em um só lugar, se organize como preferir.", + colorVar: "var(--data-5)", }, { icon: RiPercentLine, title: "Parcelamentos avançados", description: - "Controle completo de compras parceladas. Antecipe parcelas com cálculo automático de desconto. Veja análise consolidada de todas as parcelas em aberto.", + "Controle compras parceladas e antecipe parcelas com cálculo automático de desconto.", colorVar: "var(--data-4)", }, { icon: RiRobot2Line, title: "Insights com IA", description: - "Análises financeiras geradas por IA (Claude, GPT, Gemini). Insights personalizados sobre seus padrões de gastos e recomendações inteligentes.", - colorVar: "var(--data-8)", + "Análises por IA com insights sobre padrões de gastos e recomendações personalizadas.", + colorVar: "var(--data-6)", }, { icon: RiBarChartBoxLine, title: "Relatórios e gráficos", description: - "Dashboard com 20+ widgets interativos. Relatórios detalhados por categoria. Gráficos de evolução e comparativos. Exportação em PDF e Excel.", + "20+ widgets interativos, relatórios por categoria e exportação em PDF e Excel.", colorVar: "var(--data-5)", }, { icon: RiBankCard2Line, title: "Faturas de cartão", description: - "Cadastre seus cartões e acompanhe as faturas por período. Veja o que ainda não foi fechado. Controle limites, vencimentos e fechamentos.", + "Acompanhe faturas por período, limites e vencimentos de cada cartão.", colorVar: "var(--data-1)", }, { icon: RiTeamLine, title: "Gestão colaborativa", description: - "Compartilhe pagadores com permissões granulares (admin/viewer). Notificações automáticas por e-mail. Colabore em lançamentos compartilhados.", + "Compartilhe acesso com permissões granulares (admin/viewer) e notificações por e-mail.", colorVar: "var(--data-3)", }, ]; @@ -94,40 +94,40 @@ export const extraFeatures: FeatureItem[] = [ icon: RiPieChartLine, title: "Categorias e orçamentos", description: - "Crie categorias personalizadas e defina orçamentos mensais com indicadores visuais.", - colorVar: "var(--data-7)", + "Categorias personalizadas com orçamentos mensais e indicadores visuais de progresso.", + colorVar: "var(--data-6)", }, { icon: RiFileTextLine, title: "Anotações e tarefas", description: - "Notas de texto e listas de tarefas com checkboxes. Arquivamento para manter histórico.", + "Notas de texto e listas de tarefas com checkboxes e arquivamento.", colorVar: "var(--data-6)", }, { icon: RiCalendarLine, title: "Calendário financeiro", description: - "Visualize transações em calendário mensal. Nunca perca prazos de pagamentos.", + "Visualize transações em calendário mensal para não perder prazos.", colorVar: "var(--data-2)", }, { icon: RiDownloadCloudLine, title: "Importação em massa", - description: "Lance múltiplos lançamentos de uma vez", - colorVar: "var(--data-9)", + description: "Importe múltiplos lançamentos de uma só vez.", + colorVar: "var(--data-5)", }, { icon: RiEyeOffLine, title: "Modo privacidade", description: - "Oculte valores sensíveis com um clique. Tema dark/light. Calculadora integrada.", + "Oculte valores com um clique. Tema dark/light e calculadora integrada.", colorVar: "var(--data-4)", }, { icon: RiFlashlightLine, title: "Performance otimizada", - description: "Sistema rápido e com alta performance", + description: "Interface rápida e otimizada para uso diário.", colorVar: "var(--data-5)", }, ]; @@ -150,7 +150,7 @@ export const pwaHighlights: FeatureItem[] = [ icon: RiLayoutGridLine, title: "Acesso rápido ao que importa", description: "Dashboard, inbox e lançamentos a um toque.", - colorVar: "var(--data-9)", + colorVar: "var(--data-5)", }, { icon: RiFlashlightLine, @@ -193,7 +193,7 @@ export const companionSteps: FeatureItem[] = [ icon: RiCheckLine, title: "Revise e confirme no OpenMonetis", description: "Pré-lançamentos ficam na inbox para sua aprovação", - colorVar: "var(--data-9)", + colorVar: "var(--data-5)", }, ]; @@ -210,7 +210,7 @@ export const stackItems = [ title: "Backend", subtitle: "PostgreSQL, Drizzle ORM, Better Auth", description: "Banco relacional robusto com type-safe ORM", - colorVar: "var(--data-9)", + colorVar: "var(--data-5)", }, { icon: RiShieldCheckLine, @@ -242,7 +242,7 @@ export const whoIsItForItems: FeatureItem[] = [ title: "Quer controle total sobre seus dados", description: "Prefere hospedar seus próprios dados ao invés de depender de serviços terceiros", - colorVar: "var(--data-9)", + colorVar: "var(--data-5)", }, { icon: RiLineChartLine, @@ -270,7 +270,7 @@ export const whoIsItForItems: FeatureItem[] = [ title: "Não sou responsável por nada", description: "Não sou responsável por nada que aconteça com você ou com seus dados.", - colorVar: "var(--data-9)", + colorVar: "var(--data-5)", }, ]; @@ -280,7 +280,7 @@ export function getMetricsItems(stars: number, forks: number) { icon: RiLayoutGridLine, value: "20+", label: "Widgets no dashboard", - colorVar: "var(--data-9)", + colorVar: "var(--data-5)", }, { icon: RiShieldCheckLine, diff --git a/src/features/notes/lib/formatters.ts b/src/features/notes/lib/formatters.ts index 3cedd95..6bfdc21 100644 --- a/src/features/notes/lib/formatters.ts +++ b/src/features/notes/lib/formatters.ts @@ -40,7 +40,7 @@ export const formatNoteCreatedAt = ( value: string | Date | null | undefined, ) => { const parsed = parseNoteDate(value); - return parsed ? NOTE_CREATED_AT_FORMATTER.format(parsed) : null; + return parsed ? NOTE_CREATED_AT_FORMATTER.format(parsed) : ""; }; export const formatNoteCreatedAtLong = ( diff --git a/src/features/payers/components/details/payer-payment-method-cards.tsx b/src/features/payers/components/details/payer-payment-method-cards.tsx index 816887e..6951f8d 100644 --- a/src/features/payers/components/details/payer-payment-method-cards.tsx +++ b/src/features/payers/components/details/payer-payment-method-cards.tsx @@ -4,7 +4,7 @@ import { RiHourglass2Line, RiWallet3Line, } from "@remixicon/react"; -import { buildBillStatusLabel } from "@/features/dashboard/bills-helpers"; +import { buildBillStatusLabel } from "@/features/dashboard/bills/bills-helpers"; import { EstablishmentLogo } from "@/shared/components/entity-avatar"; import MoneyValues from "@/shared/components/money-values"; import { CardContent } from "@/shared/components/ui/card"; diff --git a/src/features/payers/components/payer-card.tsx b/src/features/payers/components/payer-card.tsx index dc95573..be0b726 100644 --- a/src/features/payers/components/payer-card.tsx +++ b/src/features/payers/components/payer-card.tsx @@ -72,7 +72,7 @@ export function PayerCard({ payer, onEdit, onRemove }: PayerCardProps) { {isReadOnly ? ( - + Somente leitura ) : null} diff --git a/src/features/payers/components/payer-dialog.tsx b/src/features/payers/components/payer-dialog.tsx index a53c792..b68330a 100644 --- a/src/features/payers/components/payer-dialog.tsx +++ b/src/features/payers/components/payer-dialog.tsx @@ -370,7 +370,7 @@ export function PayerDialog({ ) : (
      {isProcessingImage ? ( - + ... ) : ( diff --git a/src/features/payers/queries.ts b/src/features/payers/queries.ts index 0dec427..af5ddfd 100644 --- a/src/features/payers/queries.ts +++ b/src/features/payers/queries.ts @@ -9,7 +9,7 @@ import { PAYER_STATUS_OPTIONS, } from "@/shared/lib/payers/constants"; -export type PayerData = { +type PayerData = { id: string; name: string; email: string | null; diff --git a/src/features/reports/components/category-cell.tsx b/src/features/reports/components/category-cell.tsx index dd792b2..379c7aa 100644 --- a/src/features/reports/components/category-cell.tsx +++ b/src/features/reports/components/category-cell.tsx @@ -1,6 +1,6 @@ "use client"; -import { RiArrowDownSFill, RiArrowUpSFill } from "@remixicon/react"; +import { PercentageChangeIndicator } from "@/features/dashboard/components/percentage-change-indicator"; import { formatPercentageChange } from "@/features/reports/utils"; import { Tooltip, @@ -30,13 +30,9 @@ export function CategoryCell({ const absoluteChange = !isFirstMonth ? value - previousValue : null; - const isIncrease = percentageChange !== null && percentageChange > 0; - const isDecrease = percentageChange !== null && percentageChange < 0; - // Despesa: aumento é ruim (vermelho), diminuição é bom (verde) // Receita: aumento é bom (verde), diminuição é ruim (vermelho) - const isPositive = categoryType === "receita" ? isIncrease : isDecrease; - const isNegative = categoryType === "receita" ? isDecrease : isIncrease; + const positiveTrend = categoryType === "receita" ? "up" : "down"; return ( @@ -44,19 +40,12 @@ export function CategoryCell({
      {formatCurrency(value)} {!isFirstMonth && percentageChange !== null && ( -
      - {isIncrease && } - {isDecrease && } - - {formatPercentageChange(percentageChange)} - -
      + )}
      @@ -71,8 +60,14 @@ export function CategoryCell({
      0) && + "text-destructive", + (positiveTrend === "up" + ? absoluteChange !== null && absoluteChange > 0 + : absoluteChange !== null && absoluteChange < 0) && + "text-success", )} > Diferença:{" "} diff --git a/src/features/transactions/components/dialogs/anticipate-installments-dialog/anticipate-installments-dialog.tsx b/src/features/transactions/components/dialogs/anticipate-installments-dialog/anticipate-installments-dialog.tsx index e1c7d5d..910bf26 100644 --- a/src/features/transactions/components/dialogs/anticipate-installments-dialog/anticipate-installments-dialog.tsx +++ b/src/features/transactions/components/dialogs/anticipate-installments-dialog/anticipate-installments-dialog.tsx @@ -341,7 +341,7 @@ export function AnticipateInstallmentsDialog({ {/* Seção 3: Resumo */} {selectedIds.length > 0 && ( -
      +

      Resumo

      diff --git a/src/features/transactions/components/dialogs/transaction-details-dialog.tsx b/src/features/transactions/components/dialogs/transaction-details-dialog.tsx index e392aed..1545b98 100644 --- a/src/features/transactions/components/dialogs/transaction-details-dialog.tsx +++ b/src/features/transactions/components/dialogs/transaction-details-dialog.tsx @@ -82,7 +82,7 @@ export function TransactionDetailsDialog({
      -
      +

      diff --git a/src/features/transactions/components/select-items.tsx b/src/features/transactions/components/select-items.tsx index ffeefa2..336f24b 100644 --- a/src/features/transactions/components/select-items.tsx +++ b/src/features/transactions/components/select-items.tsx @@ -31,7 +31,7 @@ export function PayerSelectContent({ - + {initial} diff --git a/src/features/transactions/components/shared/anticipation-card.tsx b/src/features/transactions/components/shared/anticipation-card.tsx index 0e9ed49..ee6103c 100644 --- a/src/features/transactions/components/shared/anticipation-card.tsx +++ b/src/features/transactions/components/shared/anticipation-card.tsx @@ -148,7 +148,7 @@ export function AnticipationCard({

      {anticipation.note && ( -
      +
      Observação
      diff --git a/src/features/transactions/components/table/transactions-columns.tsx b/src/features/transactions/components/table/transactions-columns.tsx index 59327f1..69475bc 100644 --- a/src/features/transactions/components/table/transactions-columns.tsx +++ b/src/features/transactions/components/table/transactions-columns.tsx @@ -419,7 +419,7 @@ function buildColumns({ <> - + {initial} diff --git a/src/shared/components/logo-picker/logo-picker.tsx b/src/shared/components/logo-picker/logo-picker.tsx index 6d5680f..8e4d920 100644 --- a/src/shared/components/logo-picker/logo-picker.tsx +++ b/src/shared/components/logo-picker/logo-picker.tsx @@ -59,7 +59,7 @@ export function LogoPickerTrigger({ className="object-contain p-0.5" /> ) : ( - Logo + Logo )} @@ -172,7 +172,7 @@ export function LogoPickerDialog({ /> - + {logoLabel} diff --git a/src/shared/components/logo.tsx b/src/shared/components/logo.tsx index d22358f..4e53a18 100644 --- a/src/shared/components/logo.tsx +++ b/src/shared/components/logo.tsx @@ -8,6 +8,10 @@ interface LogoProps { invertTextOnDark?: boolean; /** Exibe o ícone na cor original, sem filtro preto. Apenas nos variants "full" e "compact" */ colorIcon?: boolean; + /** Classes extras aplicadas na imagem do ícone */ + iconClassName?: string; + /** Classes extras aplicadas na imagem do texto */ + textClassName?: string; } const iconFilterClass = "brightness-0 saturate-0"; @@ -17,6 +21,8 @@ export function Logo({ className, invertTextOnDark = true, colorIcon = false, + iconClassName, + textClassName, }: LogoProps) { if (variant === "compact") { return ( @@ -27,7 +33,11 @@ export function Logo({ alt="OpenMonetis" fill sizes="32px" - className={cn("object-contain", !colorIcon && iconFilterClass)} + className={cn( + "object-contain", + !colorIcon && iconFilterClass, + iconClassName, + )} priority />
      @@ -37,7 +47,11 @@ export function Logo({ alt="OpenMonetis" fill sizes="110px" - className={cn("object-contain", invertTextOnDark && "dark:invert")} + className={cn( + "object-contain", + invertTextOnDark && "dark:invert", + textClassName, + )} priority />
      diff --git a/src/shared/components/month-picker/month-navigation.tsx b/src/shared/components/month-picker/month-navigation.tsx index 87adfc7..08f8ab9 100644 --- a/src/shared/components/month-picker/month-navigation.tsx +++ b/src/shared/components/month-picker/month-navigation.tsx @@ -37,7 +37,7 @@ export default function MonthNavigation() { }; return ( - +
      -

      +
      +

      {icon} {title}

      -

      +

      {subtitle}

      diff --git a/src/shared/components/transaction-type-badge.tsx b/src/shared/components/transaction-type-badge.tsx index a66df9d..683a487 100644 --- a/src/shared/components/transaction-type-badge.tsx +++ b/src/shared/components/transaction-type-badge.tsx @@ -72,7 +72,7 @@ export function TransactionTypeBadge({ variant="outline" data-kind={normalizedKind ?? "custom"} className={cn( - "h-6 gap-1.5 rounded-full border-transparent px-2 py-0 text-xs font-medium shadow-none", + "h-6 gap-1 border-none rounded-md px-2 py-0 text-xs shadow-none", config?.className ?? "bg-muted/30 text-muted-foreground dark:bg-muted/20", className, diff --git a/src/shared/components/ui/button.tsx b/src/shared/components/ui/button.tsx index ccde789..be4bb88 100644 --- a/src/shared/components/ui/button.tsx +++ b/src/shared/components/ui/button.tsx @@ -20,7 +20,7 @@ const buttonVariants = cva( "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", navbar: - "bg-transparent text-black/75 shadow-none hover:bg-black/10 hover:text-black focus-visible:ring-black/20", + "bg-transparent text-black/75 shadow-none hover:bg-black/10 hover:text-black focus-visible:ring-black/20 dark:text-white/75 dark:hover:bg-white/10 dark:hover:text-white dark:focus-visible:ring-white/20", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", diff --git a/src/shared/components/ui/calendar.tsx b/src/shared/components/ui/calendar.tsx index ae6ad33..f45654c 100644 --- a/src/shared/components/ui/calendar.tsx +++ b/src/shared/components/ui/calendar.tsx @@ -90,7 +90,7 @@ function Calendar({ table: "w-full border-collapse", weekdays: cn("flex", defaultClassNames.weekdays), weekday: cn( - "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", + "text-muted-foreground rounded-md flex-1 font-normal text-xs select-none", defaultClassNames.weekday, ), week: cn("flex w-full mt-2", defaultClassNames.week), @@ -99,7 +99,7 @@ function Calendar({ defaultClassNames.week_number_header, ), week_number: cn( - "text-[0.8rem] select-none text-muted-foreground", + "text-xs select-none text-muted-foreground", defaultClassNames.week_number, ), day: cn( diff --git a/src/shared/components/ui/card.tsx b/src/shared/components/ui/card.tsx index 91dcdaf..270d1bf 100644 --- a/src/shared/components/ui/card.tsx +++ b/src/shared/components/ui/card.tsx @@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
      = 5 && hour < 12) return "Bom dia"; - if (hour >= 12 && hour < 18) return "Boa tarde"; - return "Boa noite"; -} - -export function getGreetingInTimeZone( +function getGreetingInTimeZone( timeZone: string, date: Date = new Date(), ): string { @@ -531,7 +498,7 @@ export function getBusinessGreeting(date: Date = new Date()): string { return getGreetingInTimeZone(OPENMONETIS_TIME_ZONE, date); } -export function formatCurrentDateInTimeZone( +function formatCurrentDateInTimeZone( timeZone: string, date: Date = new Date(), ): string { @@ -550,6 +517,3 @@ export function formatCurrentDateInTimeZone( export function formatBusinessCurrentDate(date: Date = new Date()): string { return formatCurrentDateInTimeZone(OPENMONETIS_TIME_ZONE, date); } - -// Re-export MONTH_NAMES for convenience -export { MONTH_NAMES };