mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
Padroniza copias e badges da interface
This commit is contained in:
@@ -11,10 +11,10 @@ import {
|
|||||||
import { fetchUserPreferences } from "@/features/settings/queries";
|
import { fetchUserPreferences } from "@/features/settings/queries";
|
||||||
import { TransactionsPage as LancamentosSection } from "@/features/transactions/components/page/transactions-page";
|
import { TransactionsPage as LancamentosSection } from "@/features/transactions/components/page/transactions-page";
|
||||||
import {
|
import {
|
||||||
buildTransactionWhere,
|
|
||||||
buildOptionSets,
|
buildOptionSets,
|
||||||
buildSluggedFilters,
|
buildSluggedFilters,
|
||||||
buildSlugMaps,
|
buildSlugMaps,
|
||||||
|
buildTransactionWhere,
|
||||||
extractTransactionSearchFilters,
|
extractTransactionSearchFilters,
|
||||||
getSingleParam,
|
getSingleParam,
|
||||||
mapTransactionsData,
|
mapTransactionsData,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiAddCircleLine, RiBankLine } from "@remixicon/react";
|
import { RiAddCircleFill, RiBankLine } from "@remixicon/react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -176,7 +176,7 @@ export function AccountsPage({
|
|||||||
logoOptions={logoOptions}
|
logoOptions={logoOptions}
|
||||||
trigger={
|
trigger={
|
||||||
<Button className="w-full sm:w-auto">
|
<Button className="w-full sm:w-auto">
|
||||||
<RiAddCircleLine className="size-4" />
|
<RiAddCircleFill className="size-4" />
|
||||||
Nova conta
|
Nova conta
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ export function BudgetDialog({
|
|||||||
) : (
|
) : (
|
||||||
<form className="space-y-4" onSubmit={handleSubmit}>
|
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="budget-category">Category</Label>
|
<Label htmlFor="budget-category">Categoria</Label>
|
||||||
<Select
|
<Select
|
||||||
value={formState.categoryId}
|
value={formState.categoryId}
|
||||||
onValueChange={(value) => updateField("categoryId", value)}
|
onValueChange={(value) => updateField("categoryId", value)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiAddCircleLine, RiFileCopyLine, RiFundsLine } from "@remixicon/react";
|
import { RiAddCircleFill, RiFileCopyLine, RiFundsLine } from "@remixicon/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
@@ -115,7 +115,7 @@ export function BudgetsPage({
|
|||||||
disabled={categories.length === 0}
|
disabled={categories.length === 0}
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<RiAddCircleLine className="size-4" />
|
<RiAddCircleFill className="size-4" />
|
||||||
Novo orçamento
|
Novo orçamento
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import { and, asc, eq, inArray, isNull, or, sql, sum } from "drizzle-orm";
|
import { and, asc, eq, inArray, isNull, or, sql, sum } from "drizzle-orm";
|
||||||
import {
|
import { budgets, categories, payers, transactions } from "@/db/schema";
|
||||||
type Budget,
|
|
||||||
budgets,
|
|
||||||
categories,
|
|
||||||
payers,
|
|
||||||
transactions,
|
|
||||||
} from "@/db/schema";
|
|
||||||
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
|
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
|
||||||
import { db } from "@/shared/lib/db";
|
import { db } from "@/shared/lib/db";
|
||||||
import { PAYER_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
import { PAYER_ROLE_ADMIN } from "@/shared/lib/payers/constants";
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export function DayCell({ day, onSelect, onCreate }: DayCellProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleCreateClick}
|
onClick={handleCreateClick}
|
||||||
className="flex size-6 items-center justify-center rounded-full border bg-muted text-muted-foreground transition-colors hover:bg-primary/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1"
|
className="flex size-6 items-center justify-center rounded-full bg-muted text-muted-foreground transition-colors hover:bg-primary/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1"
|
||||||
aria-label={`Criar lançamento em ${day.date}`}
|
aria-label={`Criar lançamento em ${day.date}`}
|
||||||
>
|
>
|
||||||
<RiAddLine className="size-3.5" />
|
<RiAddLine className="size-3.5" />
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export function CardFormFields({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2 sm:col-span-2">
|
<div className="flex flex-col gap-2 sm:col-span-2">
|
||||||
<Label htmlFor="card-account">FinancialAccount vinculada</Label>
|
<Label htmlFor="card-account">Conta vinculada</Label>
|
||||||
<Select
|
<Select
|
||||||
value={values.accountId}
|
value={values.accountId}
|
||||||
onValueChange={(value) => onChange("accountId", value)}
|
onValueChange={(value) => onChange("accountId", value)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiAddCircleLine, RiBankCard2Line } from "@remixicon/react";
|
import { RiAddCircleFill, RiBankCard2Line } from "@remixicon/react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -164,7 +164,7 @@ export function CardsPage({
|
|||||||
logoOptions={logoOptions}
|
logoOptions={logoOptions}
|
||||||
trigger={
|
trigger={
|
||||||
<Button className="w-full sm:w-auto">
|
<Button className="w-full sm:w-auto">
|
||||||
<RiAddCircleLine className="size-4" />
|
<RiAddCircleFill className="size-4" />
|
||||||
Novo cartão
|
Novo cartão
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RiAddCircleLine,
|
RiAddCircleFill,
|
||||||
RiDeleteBin5Line,
|
RiDeleteBin5Line,
|
||||||
RiExternalLinkLine,
|
RiExternalLinkLine,
|
||||||
RiPencilLine,
|
RiPencilLine,
|
||||||
@@ -127,7 +127,7 @@ export function CategoriesPage({ categories }: CategoriesPageProps) {
|
|||||||
defaultType={activeType}
|
defaultType={activeType}
|
||||||
trigger={
|
trigger={
|
||||||
<Button className="w-full sm:w-auto">
|
<Button className="w-full sm:w-auto">
|
||||||
<RiAddCircleLine className="size-4" />
|
<RiAddCircleFill className="size-4" />
|
||||||
Nova categoria
|
Nova categoria
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { RiArrowDownSFill, RiArrowUpSFill } from "@remixicon/react";
|
import { RiArrowDownSFill, RiArrowUpSFill } from "@remixicon/react";
|
||||||
import { TypeBadge } from "@/shared/components/type-badge";
|
import { TransactionTypeBadge } from "@/shared/components/transaction-type-badge";
|
||||||
import { Card } from "@/shared/components/ui/card";
|
import { Card } from "@/shared/components/ui/card";
|
||||||
import type { CategoryType } from "@/shared/lib/categories/constants";
|
import type { CategoryType } from "@/shared/lib/categories/constants";
|
||||||
import { currencyFormatter } from "@/shared/utils/currency";
|
import { currencyFormatter } from "@/shared/utils/currency";
|
||||||
@@ -85,7 +85,7 @@ export function CategoryDetailHeader({
|
|||||||
{category.name}
|
{category.name}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||||
<TypeBadge type={category.type} />
|
<TransactionTypeBadge kind={category.type} />
|
||||||
<span>
|
<span>
|
||||||
{transactionCount}{" "}
|
{transactionCount}{" "}
|
||||||
{transactionCount === 1 ? "lançamento" : "lançamentos"} no{" "}
|
{transactionCount === 1 ? "lançamento" : "lançamentos"} no{" "}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
sortableKeyboardCoordinates,
|
sortableKeyboardCoordinates,
|
||||||
} from "@dnd-kit/sortable";
|
} from "@dnd-kit/sortable";
|
||||||
import {
|
import {
|
||||||
RiAddCircleLine,
|
RiAddCircleFill,
|
||||||
RiCheckLine,
|
RiCheckLine,
|
||||||
RiCloseLine,
|
RiCloseLine,
|
||||||
RiDragMove2Line,
|
RiDragMove2Line,
|
||||||
@@ -198,10 +198,7 @@ export function DashboardGridEditable({
|
|||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||||
{!isEditing ? (
|
{!isEditing ? (
|
||||||
<div className="flex w-full min-w-0 flex-col gap-1 px-1 sm:w-auto sm:flex-row sm:items-center sm:gap-2">
|
<div className="flex w-full min-w-0 flex-col gap-1 sm:w-auto sm:flex-row sm:items-center sm:gap-2">
|
||||||
<span className="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
|
||||||
Ações rápidas
|
|
||||||
</span>
|
|
||||||
<div className="-mb-1 grid w-full grid-cols-3 gap-1 pb-1 sm:mb-0 sm:flex sm:w-auto sm:items-center sm:gap-2 sm:overflow-visible sm:pb-0">
|
<div className="-mb-1 grid w-full grid-cols-3 gap-1 pb-1 sm:mb-0 sm:flex sm:w-auto sm:items-center sm:gap-2 sm:overflow-visible sm:pb-0">
|
||||||
<TransactionDialog
|
<TransactionDialog
|
||||||
mode="create"
|
mode="create"
|
||||||
@@ -221,7 +218,7 @@ export function DashboardGridEditable({
|
|||||||
className="h-12 w-full min-w-0 flex-col justify-center gap-0.5 px-1.5 text-sm whitespace-normal sm:h-8 sm:w-auto sm:flex-row sm:gap-2 sm:px-3 sm:whitespace-nowrap"
|
className="h-12 w-full min-w-0 flex-col justify-center gap-0.5 px-1.5 text-sm whitespace-normal sm:h-8 sm:w-auto sm:flex-row sm:gap-2 sm:px-3 sm:whitespace-nowrap"
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-0.5">
|
<span className="flex items-center gap-0.5">
|
||||||
<RiAddCircleLine className="size-3.5 shrink-0 text-success/80" />
|
<RiAddCircleFill className="size-3.5 shrink-0 text-success/80" />
|
||||||
</span>
|
</span>
|
||||||
<span className="sm:hidden">Receita</span>
|
<span className="sm:hidden">Receita</span>
|
||||||
<span className="hidden sm:inline">Nova receita</span>
|
<span className="hidden sm:inline">Nova receita</span>
|
||||||
@@ -246,7 +243,7 @@ export function DashboardGridEditable({
|
|||||||
className="h-12 w-full min-w-0 flex-col justify-center gap-0.5 px-1.5 text-sm whitespace-normal sm:h-8 sm:w-auto sm:flex-row sm:gap-2 sm:px-3 sm:whitespace-nowrap"
|
className="h-12 w-full min-w-0 flex-col justify-center gap-0.5 px-1.5 text-sm whitespace-normal sm:h-8 sm:w-auto sm:flex-row sm:gap-2 sm:px-3 sm:whitespace-nowrap"
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-0.5">
|
<span className="flex items-center gap-0.5">
|
||||||
<RiAddCircleLine className="size-3.5 shrink-0 text-destructive/80" />
|
<RiAddCircleFill className="size-3.5 shrink-0 text-destructive/80" />
|
||||||
</span>
|
</span>
|
||||||
<span className="sm:hidden">Despesa</span>
|
<span className="sm:hidden">Despesa</span>
|
||||||
<span className="hidden sm:inline">Nova despesa</span>
|
<span className="hidden sm:inline">Nova despesa</span>
|
||||||
|
|||||||
@@ -113,10 +113,7 @@ export async function fetchDashboardPayers(
|
|||||||
}))
|
}))
|
||||||
.sort((a, b) => b.totalExpenses - a.totalExpenses);
|
.sort((a, b) => b.totalExpenses - a.totalExpenses);
|
||||||
|
|
||||||
const totalExpenses = payerList.reduce(
|
const totalExpenses = payerList.reduce((sum, p) => sum + p.totalExpenses, 0);
|
||||||
(sum, p) => sum + p.totalExpenses,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
payers: payerList,
|
payers: payerList,
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export function InsightsPage({ period, onAnalyze }: InsightsPageProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
{/* Privacy Warning */}
|
{/* Privacy Warning */}
|
||||||
<Alert className="border-none">
|
<Alert className="border-none bg-primary/15">
|
||||||
<RiAlertLine className="size-4" color="red" />
|
<RiAlertLine className="size-4" color="red" />
|
||||||
<AlertDescription className="text-sm text-card-foreground">
|
<AlertDescription className="text-sm text-card-foreground">
|
||||||
<strong>Aviso de privacidade:</strong> Ao gerar insights, seus dados
|
<strong>Aviso de privacidade:</strong> Ao gerar insights, seus dados
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiAddLine, RiDeleteBinLine } from "@remixicon/react";
|
import { RiAddCircleFill, RiDeleteBinLine } from "@remixicon/react";
|
||||||
import {
|
import {
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
useEffect,
|
useEffect,
|
||||||
@@ -329,7 +329,7 @@ export function NoteDialog({
|
|||||||
disabled={isPending || !normalize(newTaskText)}
|
disabled={isPending || !normalize(newTaskText)}
|
||||||
className="shrink-0"
|
className="shrink-0"
|
||||||
>
|
>
|
||||||
<RiAddLine className="h-4 w-4" />
|
<RiAddCircleFill className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiAddCircleLine, RiTodoLine } from "@remixicon/react";
|
import { RiAddCircleFill, RiTodoLine } from "@remixicon/react";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { archiveNoteAction, deleteNoteAction } from "@/features/notes/actions";
|
import { archiveNoteAction, deleteNoteAction } from "@/features/notes/actions";
|
||||||
@@ -208,7 +208,7 @@ export function NotesPage({ notes, archivedNotes }: NotesPageProps) {
|
|||||||
onOpenChange={handleCreateOpenChange}
|
onOpenChange={handleCreateOpenChange}
|
||||||
trigger={
|
trigger={
|
||||||
<Button className="w-full sm:w-auto">
|
<Button className="w-full sm:w-auto">
|
||||||
<RiAddCircleLine className="size-4" />
|
<RiAddCircleFill className="size-4" />
|
||||||
Nova anotação
|
Nova anotação
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export function PayerHeaderCard({
|
|||||||
{summary.periodLabel}
|
{summary.periodLabel}
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
para{" "}
|
para{" "}
|
||||||
<span className="font-medium text-foreground">
|
<span className="font-semibold text-foreground">
|
||||||
{payer.email}
|
{payer.email}
|
||||||
</span>
|
</span>
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function PagadorInfoCard({ payer }: PayerInfoCardProps) {
|
|||||||
<Card className="border gap-4">
|
<Card className="border gap-4">
|
||||||
<CardHeader className="gap-1.5">
|
<CardHeader className="gap-1.5">
|
||||||
<CardTitle className="text-lg font-semibold">
|
<CardTitle className="text-lg font-semibold">
|
||||||
Detalhes do payer
|
Detalhes do pagador
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{showSensitiveDetails
|
{showSensitiveDetails
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiAddCircleLine } from "@remixicon/react";
|
import { RiAddCircleFill } from "@remixicon/react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useMemo, useState, useTransition } from "react";
|
import { useMemo, useState, useTransition } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -126,7 +126,7 @@ export function PayersPage({ payers, avatarOptions }: PayersPageProps) {
|
|||||||
avatarOptions={avatarOptions}
|
avatarOptions={avatarOptions}
|
||||||
trigger={
|
trigger={
|
||||||
<Button className="w-full sm:w-auto">
|
<Button className="w-full sm:w-auto">
|
||||||
<RiAddCircleLine className="size-4" />
|
<RiAddCircleFill className="size-4" />
|
||||||
Novo pagador
|
Novo pagador
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { payers } from "@/db/schema";
|
import type { payers } from "@/db/schema";
|
||||||
import type {
|
import type {
|
||||||
AccountCardFilterOption,
|
AccountCardFilterOption,
|
||||||
TransactionFilterOption,
|
|
||||||
SelectOption,
|
SelectOption,
|
||||||
|
TransactionFilterOption,
|
||||||
TransactionItem,
|
TransactionItem,
|
||||||
} from "@/features/transactions/components/types";
|
} from "@/features/transactions/components/types";
|
||||||
import type { buildOptionSets } from "@/features/transactions/page-helpers";
|
import type { buildOptionSets } from "@/features/transactions/page-helpers";
|
||||||
@@ -35,10 +35,7 @@ export function buildReadOnlyOptionSets(
|
|||||||
if (item.accountId && !contaOptionsMap.has(item.accountId)) {
|
if (item.accountId && !contaOptionsMap.has(item.accountId)) {
|
||||||
contaOptionsMap.set(item.accountId, {
|
contaOptionsMap.set(item.accountId, {
|
||||||
value: item.accountId,
|
value: item.accountId,
|
||||||
label: normalizeOptionLabel(
|
label: normalizeOptionLabel(item.contaName, "Conta sem nome"),
|
||||||
item.contaName,
|
|
||||||
"FinancialAccount sem nome",
|
|
||||||
),
|
|
||||||
slug: item.accountId,
|
slug: item.accountId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function CardCategoryBreakdown({ data }: CardCategoryBreakdownProps) {
|
|||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="flex items-center gap-1.5 text-base">
|
<CardTitle className="flex items-center gap-1.5 text-base">
|
||||||
<RiPieChartLine className="size-4 text-primary" />
|
<RiPieChartLine className="size-4 text-primary" />
|
||||||
Gastos por Category
|
Gastos por Categoria
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@@ -45,7 +45,7 @@ export function CardCategoryBreakdown({ data }: CardCategoryBreakdownProps) {
|
|||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="flex items-center gap-1.5 text-base">
|
<CardTitle className="flex items-center gap-1.5 text-base">
|
||||||
<RiPieChartLine className="size-4 text-primary" />
|
<RiPieChartLine className="size-4 text-primary" />
|
||||||
Gastos por Category
|
Gastos por Categoria
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export function CategoryReportChart({ data }: CategoryReportChartProps) {
|
|||||||
<Card className="pt-0">
|
<Card className="pt-0">
|
||||||
<CardHeader className="flex items-center gap-2 space-y-0 border-b py-5 sm:flex-row">
|
<CardHeader className="flex items-center gap-2 space-y-0 border-b py-5 sm:flex-row">
|
||||||
<div className="grid flex-1 gap-1">
|
<div className="grid flex-1 gap-1">
|
||||||
<CardTitle>Evolução por Category</CardTitle>
|
<CardTitle>Evolução por Categoria</CardTitle>
|
||||||
<CardDescription>{periodLabel}</CardDescription>
|
<CardDescription>{periodLabel}</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Select value={limit} onValueChange={setLimit}>
|
<Select value={limit} onValueChange={setLimit}>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function CategoryReportExport({
|
|||||||
|
|
||||||
// Build CSV content
|
// Build CSV content
|
||||||
const headers = [
|
const headers = [
|
||||||
"Category",
|
"Categoria",
|
||||||
...data.periods.map(formatPeriodLabel),
|
...data.periods.map(formatPeriodLabel),
|
||||||
"Total",
|
"Total",
|
||||||
];
|
];
|
||||||
@@ -129,7 +129,7 @@ export function CategoryReportExport({
|
|||||||
|
|
||||||
// Build data array
|
// Build data array
|
||||||
const headers = [
|
const headers = [
|
||||||
"Category",
|
"Categoria",
|
||||||
...data.periods.map(formatPeriodLabel),
|
...data.periods.map(formatPeriodLabel),
|
||||||
"Total",
|
"Total",
|
||||||
];
|
];
|
||||||
@@ -249,7 +249,7 @@ export function CategoryReportExport({
|
|||||||
|
|
||||||
// Build table data
|
// Build table data
|
||||||
const headers = [
|
const headers = [
|
||||||
["Category", ...data.periods.map(formatPeriodLabel), "Total"],
|
["Categoria", ...data.periods.map(formatPeriodLabel), "Total"],
|
||||||
];
|
];
|
||||||
const body: string[][] = [];
|
const body: string[][] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export function CategoryReportFilters({
|
|||||||
|
|
||||||
const selectedText =
|
const selectedText =
|
||||||
selectedCategories.length === 0
|
selectedCategories.length === 0
|
||||||
? "Category"
|
? "Categoria"
|
||||||
: selectedCategories.length === categories.length
|
: selectedCategories.length === categories.length
|
||||||
? "Todas"
|
? "Todas"
|
||||||
: selectedCategories.length === 1
|
: selectedCategories.length === 1
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function CategoryTable({
|
|||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="w-[240px] min-w-[240px] font-bold">
|
<TableHead className="w-[240px] min-w-[240px] font-bold">
|
||||||
Category
|
Categoria
|
||||||
</TableHead>
|
</TableHead>
|
||||||
{periods.map((period) => (
|
{periods.map((period) => (
|
||||||
<TableHead
|
<TableHead
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export function EstablishmentsList({
|
|||||||
.map((cat, catIndex) => (
|
.map((cat, catIndex) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={catIndex}
|
key={catIndex}
|
||||||
variant="secondary"
|
variant="outline"
|
||||||
className="text-xs px-1.5 py-0 h-5"
|
className="text-xs px-1.5 py-0 h-5"
|
||||||
>
|
>
|
||||||
{cat.name}
|
{cat.name}
|
||||||
|
|||||||
@@ -11,17 +11,15 @@ type HighlightsCardsProps = {
|
|||||||
export function HighlightsCards({ summary }: HighlightsCardsProps) {
|
export function HighlightsCards({ summary }: HighlightsCardsProps) {
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
<Card className="bg-linear-to-br from-violet-50 to-violet-50/50 dark:from-violet-950/20 dark:to-violet-950/10">
|
<Card className="">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center justify-center size-10 rounded-xl bg-violet-100 dark:bg-violet-900/40">
|
<div className="flex items-center justify-center size-10 rounded-md bg-primary">
|
||||||
<RiTrophyLine className="size-5 text-violet-600 dark:text-violet-400" />
|
<RiTrophyLine className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-xs text-violet-700/80 dark:text-violet-400/80 font-medium">
|
<p className="text-xs font-bold">Mais Frequente</p>
|
||||||
Mais Frequente
|
<p className="font-bold text-2xl truncate">
|
||||||
</p>
|
|
||||||
<p className="font-bold text-xl text-violet-900 dark:text-violet-100 truncate">
|
|
||||||
{summary.mostFrequent || "—"}
|
{summary.mostFrequent || "—"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,17 +27,15 @@ export function HighlightsCards({ summary }: HighlightsCardsProps) {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="bg-linear-to-br from-red-50 to-rose-50/50 dark:from-red-950/20 dark:to-rose-950/10">
|
<Card className="">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex items-center justify-center size-10 rounded-xl bg-red-100 dark:bg-red-900/40">
|
<div className="flex items-center justify-center size-10 rounded-md bg-primary">
|
||||||
<RiFireLine className="size-5 text-red-600 dark:text-red-400" />
|
<RiFireLine className="size-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-xs text-red-700/80 dark:text-red-400/80 font-medium">
|
<p className="text-xs font-bold">Maior Gasto Total</p>
|
||||||
Maior Gasto Total
|
<p className="font-bold text-2xl truncate">
|
||||||
</p>
|
|
||||||
<p className="font-bold text-xl text-red-900 dark:text-red-100 truncate">
|
|
||||||
{summary.highestSpending || "—"}
|
{summary.highestSpending || "—"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RiAddLine,
|
RiAddCircleFill,
|
||||||
RiAlertLine,
|
RiAlertLine,
|
||||||
RiCheckLine,
|
RiCheckLine,
|
||||||
RiDeleteBinLine,
|
RiDeleteBinLine,
|
||||||
@@ -154,7 +154,7 @@ export function ApiTokensForm({ tokens }: ApiTokensFormProps) {
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button size="sm">
|
<Button size="sm">
|
||||||
<RiAddLine className="h-4 w-4 mr-1" />
|
<RiAddCircleFill className="h-4 w-4 mr-1" />
|
||||||
Novo Token
|
Novo Token
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RiAddLine,
|
RiAddCircleFill,
|
||||||
RiAlertLine,
|
RiAlertLine,
|
||||||
RiDeleteBinLine,
|
RiDeleteBinLine,
|
||||||
RiFingerprintLine,
|
RiFingerprintLine,
|
||||||
@@ -210,7 +210,7 @@ export function PasskeysForm() {
|
|||||||
>
|
>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button size="sm" disabled={isMutating || !passkeySupported}>
|
<Button size="sm" disabled={isMutating || !passkeySupported}>
|
||||||
<RiAddLine className="h-4 w-4 mr-1" />
|
<RiAddCircleFill className="h-4 w-4 mr-1" />
|
||||||
Adicionar
|
Adicionar
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ export function AnticipateInstallmentsDialog({
|
|||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field className="gap-1">
|
<Field className="gap-1">
|
||||||
<FieldLabel htmlFor="anticipation-pagador">Payer</FieldLabel>
|
<FieldLabel htmlFor="anticipation-pagador">Pagador</FieldLabel>
|
||||||
<FieldContent>
|
<FieldContent>
|
||||||
<Select
|
<Select
|
||||||
value={formState.payerId}
|
value={formState.payerId}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/shared/components/ui/select";
|
} from "@/shared/components/ui/select";
|
||||||
import {
|
import {
|
||||||
CategorySelectContent,
|
|
||||||
AccountCardSelectContent,
|
AccountCardSelectContent,
|
||||||
|
CategorySelectContent,
|
||||||
PayerSelectContent,
|
PayerSelectContent,
|
||||||
} from "../select-items";
|
} from "../select-items";
|
||||||
import type { SelectOption, TransactionItem } from "../types";
|
import type { SelectOption, TransactionItem } from "../types";
|
||||||
@@ -203,7 +203,7 @@ export function BulkImportDialog({
|
|||||||
|
|
||||||
<form className="space-y-4" onSubmit={handleSubmit}>
|
<form className="space-y-4" onSubmit={handleSubmit}>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="pagador">Payer *</Label>
|
<Label htmlFor="pagador">Pagador *</Label>
|
||||||
<Select value={payerId} onValueChange={setPagadorId}>
|
<Select value={payerId} onValueChange={setPagadorId}>
|
||||||
<SelectTrigger id="pagador" className="w-full">
|
<SelectTrigger id="pagador" className="w-full">
|
||||||
<SelectValue placeholder="Selecione o pagador">
|
<SelectValue placeholder="Selecione o pagador">
|
||||||
@@ -235,7 +235,7 @@ export function BulkImportDialog({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="categoria">Category *</Label>
|
<Label htmlFor="categoria">Categoria *</Label>
|
||||||
<Select value={categoryId} onValueChange={setCategoriaId}>
|
<Select value={categoryId} onValueChange={setCategoriaId}>
|
||||||
<SelectTrigger id="categoria" className="w-full">
|
<SelectTrigger id="categoria" className="w-full">
|
||||||
<SelectValue placeholder="Selecione a categoria">
|
<SelectValue placeholder="Selecione a categoria">
|
||||||
@@ -274,7 +274,7 @@ export function BulkImportDialog({
|
|||||||
{hasNonCredit && (
|
{hasNonCredit && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="conta">
|
<Label htmlFor="conta">
|
||||||
FinancialAccount {hasCredit ? "(para não cartão)" : "*"}
|
Conta {hasCredit ? "(para não cartão)" : "*"}
|
||||||
</Label>
|
</Label>
|
||||||
<Select value={accountId} onValueChange={setContaId}>
|
<Select value={accountId} onValueChange={setContaId}>
|
||||||
<SelectTrigger id="conta" className="w-full">
|
<SelectTrigger id="conta" className="w-full">
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ import {
|
|||||||
periodToDate,
|
periodToDate,
|
||||||
} from "@/shared/utils/period";
|
} from "@/shared/utils/period";
|
||||||
import {
|
import {
|
||||||
CategorySelectContent,
|
|
||||||
AccountCardSelectContent,
|
AccountCardSelectContent,
|
||||||
|
CategorySelectContent,
|
||||||
PayerSelectContent,
|
PayerSelectContent,
|
||||||
PaymentMethodSelectContent,
|
PaymentMethodSelectContent,
|
||||||
TransactionTypeSelectContent,
|
TransactionTypeSelectContent,
|
||||||
@@ -413,7 +413,7 @@ export function MassAddDialog({
|
|||||||
{/* FinancialAccount (for non-credit-card methods) */}
|
{/* FinancialAccount (for non-credit-card methods) */}
|
||||||
{!isCartaoSelected ? (
|
{!isCartaoSelected ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="conta">FinancialAccount</Label>
|
<Label htmlFor="conta">Conta</Label>
|
||||||
<Select value={accountId} onValueChange={setContaId}>
|
<Select value={accountId} onValueChange={setContaId}>
|
||||||
<SelectTrigger id="conta" className="w-full">
|
<SelectTrigger id="conta" className="w-full">
|
||||||
<SelectValue placeholder="Selecione">
|
<SelectValue placeholder="Selecione">
|
||||||
@@ -534,7 +534,7 @@ export function MassAddDialog({
|
|||||||
htmlFor={`pagador-${transaction.id}`}
|
htmlFor={`pagador-${transaction.id}`}
|
||||||
className="sr-only"
|
className="sr-only"
|
||||||
>
|
>
|
||||||
Payer {index + 1}
|
Pagador {index + 1}
|
||||||
</Label>
|
</Label>
|
||||||
<Select
|
<Select
|
||||||
value={transaction.payerId}
|
value={transaction.payerId}
|
||||||
@@ -546,7 +546,7 @@ export function MassAddDialog({
|
|||||||
id={`pagador-${transaction.id}`}
|
id={`pagador-${transaction.id}`}
|
||||||
className="w-32 truncate"
|
className="w-32 truncate"
|
||||||
>
|
>
|
||||||
<SelectValue placeholder="Payer">
|
<SelectValue placeholder="Pagador">
|
||||||
{transaction.payerId &&
|
{transaction.payerId &&
|
||||||
(() => {
|
(() => {
|
||||||
const selectedOption = payerOptions.find(
|
const selectedOption = payerOptions.find(
|
||||||
@@ -579,7 +579,7 @@ export function MassAddDialog({
|
|||||||
htmlFor={`categoria-${transaction.id}`}
|
htmlFor={`categoria-${transaction.id}`}
|
||||||
className="sr-only"
|
className="sr-only"
|
||||||
>
|
>
|
||||||
Category {index + 1}
|
Categoria {index + 1}
|
||||||
</Label>
|
</Label>
|
||||||
<Select
|
<Select
|
||||||
value={transaction.categoryId}
|
value={transaction.categoryId}
|
||||||
@@ -591,7 +591,7 @@ export function MassAddDialog({
|
|||||||
id={`categoria-${transaction.id}`}
|
id={`categoria-${transaction.id}`}
|
||||||
className="w-32 truncate"
|
className="w-32 truncate"
|
||||||
>
|
>
|
||||||
<SelectValue placeholder="Category" />
|
<SelectValue placeholder="Categoria" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{groupedCategorias.map((group) => (
|
{groupedCategorias.map((group) => (
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import {
|
|||||||
formatCondition,
|
formatCondition,
|
||||||
formatDate,
|
formatDate,
|
||||||
formatPeriod,
|
formatPeriod,
|
||||||
getTransactionBadgeVariant,
|
|
||||||
} from "@/features/transactions/formatting-helpers";
|
} from "@/features/transactions/formatting-helpers";
|
||||||
import { Badge } from "@/shared/components/ui/badge";
|
import { TransactionTypeBadge } from "@/shared/components/transaction-type-badge";
|
||||||
import { Button } from "@/shared/components/ui/button";
|
import { Button } from "@/shared/components/ui/button";
|
||||||
import {
|
import {
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -93,12 +92,12 @@ export function TransactionDetailsDialog({
|
|||||||
</li>
|
</li>
|
||||||
|
|
||||||
<DetailRow
|
<DetailRow
|
||||||
label={transaction.cartaoName ? "Cartão" : "FinancialAccount"}
|
label={transaction.cartaoName ? "Cartão" : "Conta"}
|
||||||
value={transaction.cartaoName ?? transaction.contaName ?? "—"}
|
value={transaction.cartaoName ?? transaction.contaName ?? "—"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DetailRow
|
<DetailRow
|
||||||
label="Category"
|
label="Categoria"
|
||||||
value={transaction.categoriaName ?? "—"}
|
value={transaction.categoriaName ?? "—"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -106,19 +105,13 @@ export function TransactionDetailsDialog({
|
|||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
Tipo de Transação
|
Tipo de Transação
|
||||||
</span>
|
</span>
|
||||||
<span className="capitalize">
|
<TransactionTypeBadge
|
||||||
<Badge
|
kind={
|
||||||
variant={getTransactionBadgeVariant(
|
transaction.categoriaName === "Saldo inicial"
|
||||||
transaction.categoriaName === "Saldo inicial"
|
? "Saldo inicial"
|
||||||
? "Saldo inicial"
|
: transaction.transactionType
|
||||||
: transaction.transactionType,
|
}
|
||||||
)}
|
/>
|
||||||
>
|
|
||||||
{transaction.categoriaName === "Saldo inicial"
|
|
||||||
? "Saldo Inicial"
|
|
||||||
: transaction.transactionType}
|
|
||||||
</Badge>
|
|
||||||
</span>
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<DetailRow
|
<DetailRow
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function CategorySection({
|
|||||||
showTransactionTypeField ? "md:w-1/2" : "md:w-full",
|
showTransactionTypeField ? "md:w-1/2" : "md:w-full",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Label htmlFor="categoria">Category</Label>
|
<Label htmlFor="categoria">Categoria</Label>
|
||||||
<Select
|
<Select
|
||||||
value={formState.categoryId}
|
value={formState.categoryId}
|
||||||
onValueChange={(value) => onFieldChange("categoryId", value)}
|
onValueChange={(value) => onFieldChange("categoryId", value)}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function PayerSection({
|
|||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||||
<div className="w-full space-y-1">
|
<div className="w-full space-y-1">
|
||||||
<Label htmlFor="payer">Payer</Label>
|
<Label htmlFor="payer">Pagador</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Select
|
<Select
|
||||||
value={formState.payerId}
|
value={formState.payerId}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export function PaymentMethodSection({
|
|||||||
!isUpdateMode ? "md:w-1/2" : "md:w-full",
|
!isUpdateMode ? "md:w-1/2" : "md:w-full",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Label htmlFor="conta">FinancialAccount</Label>
|
<Label htmlFor="conta">Conta</Label>
|
||||||
<Select
|
<Select
|
||||||
value={formState.accountId}
|
value={formState.accountId}
|
||||||
onValueChange={(value) => onFieldChange("accountId", value)}
|
onValueChange={(value) => onFieldChange("accountId", value)}
|
||||||
@@ -308,7 +308,7 @@ export function PaymentMethodSection({
|
|||||||
!isUpdateMode ? "md:w-1/2" : "md:w-full",
|
!isUpdateMode ? "md:w-1/2" : "md:w-full",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Label htmlFor="contaUpdate">FinancialAccount</Label>
|
<Label htmlFor="contaUpdate">Conta</Label>
|
||||||
<Select
|
<Select
|
||||||
value={formState.accountId}
|
value={formState.accountId}
|
||||||
onValueChange={(value) => onFieldChange("accountId", value)}
|
onValueChange={(value) => onFieldChange("accountId", value)}
|
||||||
|
|||||||
@@ -134,14 +134,14 @@ export function AnticipationCard({
|
|||||||
|
|
||||||
{anticipation.payer && (
|
{anticipation.payer && (
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-muted-foreground">Payer</dt>
|
<dt className="text-muted-foreground">Pagador</dt>
|
||||||
<dd className="mt-1 font-medium">{anticipation.payer.name}</dd>
|
<dd className="mt-1 font-medium">{anticipation.payer.name}</dd>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{anticipation.category && (
|
{anticipation.category && (
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-muted-foreground">Category</dt>
|
<dt className="text-muted-foreground">Categoria</dt>
|
||||||
<dd className="mt-1 font-medium">{anticipation.category.name}</dd>
|
<dd className="mt-1 font-medium">{anticipation.category.name}</dd>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ export function TransactionsFilters({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium">Payer</label>
|
<label className="text-sm font-medium">Pagador</label>
|
||||||
<Select
|
<Select
|
||||||
value={getParamValue("payer")}
|
value={getParamValue("payer")}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@@ -409,7 +409,7 @@ export function TransactionsFilters({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium">Category</label>
|
<label className="text-sm font-medium">Categoria</label>
|
||||||
<Popover
|
<Popover
|
||||||
open={categoryOpen}
|
open={categoryOpen}
|
||||||
onOpenChange={setCategoryOpen}
|
onOpenChange={setCategoryOpen}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import { CategoryIcon } from "@/features/categories/components/category-icon";
|
|||||||
import { DEFAULT_LANCAMENTOS_COLUMN_ORDER } from "@/features/transactions/column-order";
|
import { DEFAULT_LANCAMENTOS_COLUMN_ORDER } from "@/features/transactions/column-order";
|
||||||
import { EmptyState } from "@/shared/components/empty-state";
|
import { EmptyState } from "@/shared/components/empty-state";
|
||||||
import MoneyValues from "@/shared/components/money-values";
|
import MoneyValues from "@/shared/components/money-values";
|
||||||
import { TypeBadge } from "@/shared/components/type-badge";
|
import { TransactionTypeBadge } from "@/shared/components/transaction-type-badge";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
AvatarFallback,
|
AvatarFallback,
|
||||||
@@ -302,8 +302,8 @@ const buildColumns = ({
|
|||||||
: row.original.transactionType;
|
: row.original.transactionType;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypeBadge
|
<TransactionTypeBadge
|
||||||
type={
|
kind={
|
||||||
type as "Despesa" | "Receita" | "Transferência" | "Saldo inicial"
|
type as "Despesa" | "Receita" | "Transferência" | "Saldo inicial"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -458,7 +458,7 @@ const buildColumns = ({
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side="top">
|
||||||
{isCartao ? "Cartão" : "FinancialAccount"}: {label}
|
{isCartao ? "Cartão" : "Conta"}: {label}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@@ -484,7 +484,7 @@ const buildColumns = ({
|
|||||||
</Link>
|
</Link>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="top">
|
<TooltipContent side="top">
|
||||||
{isCartao ? "Cartão" : "FinancialAccount"}: {label}
|
{isCartao ? "Cartão" : "Conta"}: {label}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@@ -607,7 +607,7 @@ const buildColumns = ({
|
|||||||
row.original.userId !== currentUserId && (
|
row.original.userId !== currentUserId && (
|
||||||
<DropdownMenuItem onSelect={() => handleImport(row.original)}>
|
<DropdownMenuItem onSelect={() => handleImport(row.original)}>
|
||||||
<RiFileCopyLine className="size-4" />
|
<RiFileCopyLine className="size-4" />
|
||||||
Importar para Minha FinancialAccount
|
Importar para Minha Conta
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
{row.original.userId === currentUserId && (
|
{row.original.userId === currentUserId && (
|
||||||
@@ -866,14 +866,14 @@ export function TransactionsTable({
|
|||||||
onClick={() => onCreate("Receita")}
|
onClick={() => onCreate("Receita")}
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<RiAddCircleLine className="size-4" />
|
<RiAddCircleFill className="size-4" />
|
||||||
Nova Receita
|
Nova Receita
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onCreate("Despesa")}
|
onClick={() => onCreate("Despesa")}
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<RiAddCircleLine className="size-4" />
|
<RiAddCircleFill className="size-4" />
|
||||||
Nova Despesa
|
Nova Despesa
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
@@ -887,7 +887,7 @@ export function TransactionsTable({
|
|||||||
size="icon"
|
size="icon"
|
||||||
className="hidden size-9 sm:inline-flex"
|
className="hidden size-9 sm:inline-flex"
|
||||||
>
|
>
|
||||||
<RiAddCircleFill className="size-4" />
|
<RiAddCircleLine className="size-4" />
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
Adicionar múltiplos lançamentos
|
Adicionar múltiplos lançamentos
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -72,21 +72,6 @@ export function formatCondition(value?: string | null): string {
|
|||||||
return capitalize(value);
|
return capitalize(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the badge variant for a transaction type
|
|
||||||
* @param type - Transaction type (Receita/Despesa)
|
|
||||||
* @returns Badge variant
|
|
||||||
*/
|
|
||||||
export function getTransactionBadgeVariant(
|
|
||||||
type?: string | null,
|
|
||||||
): "default" | "destructive" | "secondary" {
|
|
||||||
if (!type) return "secondary";
|
|
||||||
const normalized = type.toLowerCase();
|
|
||||||
return normalized === "receita" || normalized === "saldo inicial"
|
|
||||||
? "default"
|
|
||||||
: "destructive";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats currency value
|
* Formats currency value
|
||||||
* @param value - Numeric value
|
* @param value - Numeric value
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function ExpandableWidgetCard({
|
|||||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex justify-center bg-linear-to-t from-card to-transparent pt-12 pb-6">
|
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex justify-center bg-linear-to-t from-card to-transparent pt-12 pb-6">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="pointer-events-auto rounded-full text-xs"
|
className="pointer-events-auto rounded-full text-xs backdrop-blur-sm bg-primary/10"
|
||||||
onClick={() => setIsOpen(true)}
|
onClick={() => setIsOpen(true)}
|
||||||
aria-label="Expandir para ver todo o conteúdo"
|
aria-label="Expandir para ver todo o conteúdo"
|
||||||
>
|
>
|
||||||
|
|||||||
88
src/shared/components/transaction-type-badge.tsx
Normal file
88
src/shared/components/transaction-type-badge.tsx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { Badge } from "@/shared/components/ui/badge";
|
||||||
|
import { cn } from "@/shared/utils/ui";
|
||||||
|
import StatusDot from "./status-dot";
|
||||||
|
|
||||||
|
type FinancialKind =
|
||||||
|
| "receita"
|
||||||
|
| "despesa"
|
||||||
|
| "Receita"
|
||||||
|
| "Despesa"
|
||||||
|
| "Transferência"
|
||||||
|
| "transferência"
|
||||||
|
| "Saldo inicial"
|
||||||
|
| "Saldo Inicial";
|
||||||
|
|
||||||
|
type FinancialKindKey =
|
||||||
|
| "receita"
|
||||||
|
| "despesa"
|
||||||
|
| "transferência"
|
||||||
|
| "saldo inicial";
|
||||||
|
|
||||||
|
type TransactionTypeBadgeProps = {
|
||||||
|
kind: FinancialKind | string;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BadgeConfig = {
|
||||||
|
label: string;
|
||||||
|
className: string;
|
||||||
|
dotClassName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BADGE_CONFIG: Record<FinancialKindKey, BadgeConfig> = {
|
||||||
|
receita: {
|
||||||
|
label: "Receita",
|
||||||
|
className: "bg-success/10 text-success dark:bg-success/10",
|
||||||
|
dotClassName: "bg-success/80",
|
||||||
|
},
|
||||||
|
despesa: {
|
||||||
|
label: "Despesa",
|
||||||
|
className: "bg-destructive/10 text-destructive dark:bg-destructive/10",
|
||||||
|
dotClassName: "bg-destructive/80",
|
||||||
|
},
|
||||||
|
transferência: {
|
||||||
|
label: "Transferência",
|
||||||
|
className: "bg-info/10 text-info dark:bg-info/10",
|
||||||
|
dotClassName: "bg-info/80",
|
||||||
|
},
|
||||||
|
"saldo inicial": {
|
||||||
|
label: "Saldo Inicial",
|
||||||
|
className: "bg-success/10 text-success dark:bg-success/10",
|
||||||
|
dotClassName: "bg-success/80",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeKind(value: string): FinancialKindKey | null {
|
||||||
|
const normalizedValue = value.trim().toLowerCase();
|
||||||
|
return normalizedValue in BADGE_CONFIG
|
||||||
|
? (normalizedValue as FinancialKindKey)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TransactionTypeBadge({
|
||||||
|
kind,
|
||||||
|
className,
|
||||||
|
}: TransactionTypeBadgeProps) {
|
||||||
|
const normalizedKind = normalizeKind(kind);
|
||||||
|
const config = normalizedKind ? BADGE_CONFIG[normalizedKind] : null;
|
||||||
|
const label = config?.label ?? kind;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
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",
|
||||||
|
config?.className ??
|
||||||
|
"bg-muted/30 text-muted-foreground dark:bg-muted/20",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<StatusDot
|
||||||
|
color={config?.dotClassName ?? "bg-muted-foreground/60"}
|
||||||
|
className="size-1.5"
|
||||||
|
/>
|
||||||
|
<span>{label}</span>
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { Badge } from "@/shared/components/ui/badge";
|
|
||||||
import { cn } from "@/shared/utils/ui";
|
|
||||||
import StatusDot from "./status-dot";
|
|
||||||
|
|
||||||
type TypeBadgeType =
|
|
||||||
| "receita"
|
|
||||||
| "despesa"
|
|
||||||
| "Receita"
|
|
||||||
| "Despesa"
|
|
||||||
| "Transferência"
|
|
||||||
| "transferência"
|
|
||||||
| "Saldo inicial"
|
|
||||||
| "Saldo Inicial";
|
|
||||||
|
|
||||||
interface TypeBadgeProps {
|
|
||||||
type: TypeBadgeType | string;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TYPE_LABELS: Record<string, string> = {
|
|
||||||
receita: "Receita",
|
|
||||||
despesa: "Despesa",
|
|
||||||
Receita: "Receita",
|
|
||||||
Despesa: "Despesa",
|
|
||||||
Transferência: "Transferência",
|
|
||||||
transferência: "Transferência",
|
|
||||||
"Saldo inicial": "Saldo Inicial",
|
|
||||||
"Saldo Inicial": "Saldo Inicial",
|
|
||||||
};
|
|
||||||
|
|
||||||
export function TypeBadge({ type, className }: TypeBadgeProps) {
|
|
||||||
const normalizedType = type.toLowerCase();
|
|
||||||
const isReceita = normalizedType === "receita";
|
|
||||||
const isTransferencia = normalizedType === "transferência";
|
|
||||||
const isSaldoInicial = normalizedType === "saldo inicial";
|
|
||||||
const label = TYPE_LABELS[type] || type;
|
|
||||||
|
|
||||||
const colorClass = isTransferencia
|
|
||||||
? "text-info"
|
|
||||||
: isReceita || isSaldoInicial
|
|
||||||
? "text-success"
|
|
||||||
: "text-destructive";
|
|
||||||
|
|
||||||
const dotColor = isTransferencia
|
|
||||||
? "bg-info"
|
|
||||||
: isReceita || isSaldoInicial
|
|
||||||
? "bg-success"
|
|
||||||
: "bg-destructive";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Badge
|
|
||||||
variant={"outline"}
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-1 px-2 text-xs",
|
|
||||||
colorClass,
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<StatusDot color={dotColor} />
|
|
||||||
{label}
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -117,7 +117,7 @@ function CommandGroup({
|
|||||||
<CommandPrimitive.Group
|
<CommandPrimitive.Group
|
||||||
data-slot="command-group"
|
data-slot="command-group"
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-foreground **:[[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium",
|
"text-popover-foreground **:[[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default function WidgetCard({
|
|||||||
<span className="size-4">{icon}</span>
|
<span className="size-4">{icon}</span>
|
||||||
{title}
|
{title}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-muted-foreground text-sm lowercase mt-2">
|
<CardDescription className="text-muted-foreground text-sm lowercase mt-1.5 tracking-tight">
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const DEFAULT_CATEGORIES: DefaultCategory[] = [
|
|||||||
{ name: "Outras receitas", type: "receita", icon: "RiMore2Line" },
|
{ name: "Outras receitas", type: "receita", icon: "RiMore2Line" },
|
||||||
{ name: "Saldo inicial", type: "receita", icon: "RiWallet2Line" },
|
{ name: "Saldo inicial", type: "receita", icon: "RiWallet2Line" },
|
||||||
|
|
||||||
// Category especial para transferências entre financialAccounts
|
// Category especial para transferências entre Contas
|
||||||
{
|
{
|
||||||
name: "Transferência interna",
|
name: "Transferência interna",
|
||||||
type: "receita",
|
type: "receita",
|
||||||
|
|||||||
Reference in New Issue
Block a user