forked from git.gladyson/openmonetis
feat: adição de novos ícones SVG e configuração do ambiente
- Adicionados ícones SVG para ChatGPT, Claude, Gemini e OpenRouter - Implementados ícones para modos claro e escuro do ChatGPT - Criado script de inicialização para PostgreSQL com extensão pgcrypto - Adicionado script de configuração de ambiente que faz backup do .env - Configurado tsconfig.json para TypeScript com opções de compilação
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { DatePicker } from "@/components/ui/date-picker";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { CurrencyInput } from "@/components/ui/currency-input";
|
||||
import { CalculatorDialogButton } from "@/components/calculadora/calculator-dialog";
|
||||
import { RiCalculatorLine } from "@remixicon/react";
|
||||
import { EstabelecimentoInput } from "../../shared/estabelecimento-input";
|
||||
import type { BasicFieldsSectionProps } from "./lancamento-dialog-types";
|
||||
|
||||
export function BasicFieldsSection({
|
||||
formState,
|
||||
onFieldChange,
|
||||
estabelecimentos,
|
||||
monthOptions,
|
||||
}: BasicFieldsSectionProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||
<div className="w-1/2 space-y-1">
|
||||
<Label htmlFor="purchaseDate">Data da transação</Label>
|
||||
<DatePicker
|
||||
id="purchaseDate"
|
||||
value={formState.purchaseDate}
|
||||
onChange={(value) => onFieldChange("purchaseDate", value)}
|
||||
placeholder="Data da transação"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-1/2 space-y-1">
|
||||
<Label htmlFor="period">Período</Label>
|
||||
<Select
|
||||
value={formState.period}
|
||||
onValueChange={(value) => onFieldChange("period", value)}
|
||||
>
|
||||
<SelectTrigger id="period" className="w-full">
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{monthOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||
<div className="w-1/2 space-y-1">
|
||||
<Label htmlFor="name">Estabelecimento</Label>
|
||||
<EstabelecimentoInput
|
||||
id="name"
|
||||
value={formState.name}
|
||||
onChange={(value) => onFieldChange("name", value)}
|
||||
estabelecimentos={estabelecimentos}
|
||||
placeholder="Ex.: Padaria"
|
||||
maxLength={20}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-1/2 space-y-1">
|
||||
<Label htmlFor="amount">Valor</Label>
|
||||
<div className="relative">
|
||||
<CurrencyInput
|
||||
id="amount"
|
||||
value={formState.amount}
|
||||
onValueChange={(value) => onFieldChange("amount", value)}
|
||||
placeholder="R$ 0,00"
|
||||
required
|
||||
className="pr-10"
|
||||
/>
|
||||
<CalculatorDialogButton
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
className="absolute right-1 top-1/2 h-7 w-7 -translate-y-1/2"
|
||||
>
|
||||
<RiCalculatorLine className="h-4 w-4 text-muted-foreground" />
|
||||
</CalculatorDialogButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { DatePicker } from "@/components/ui/date-picker";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import type { BoletoFieldsSectionProps } from "./lancamento-dialog-types";
|
||||
|
||||
export function BoletoFieldsSection({
|
||||
formState,
|
||||
onFieldChange,
|
||||
showPaymentDate,
|
||||
}: BoletoFieldsSectionProps) {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-2 w-full",
|
||||
showPaymentDate ? "md:w-1/2" : "md:w-full"
|
||||
)}
|
||||
>
|
||||
<Label htmlFor="dueDate">Vencimento do boleto</Label>
|
||||
<DatePicker
|
||||
id="dueDate"
|
||||
value={formState.dueDate}
|
||||
onChange={(value) => onFieldChange("dueDate", value)}
|
||||
placeholder="Selecione o vencimento"
|
||||
/>
|
||||
</div>
|
||||
{showPaymentDate ? (
|
||||
<div className="space-y-2 w-full md:w-1/2">
|
||||
<Label htmlFor="boletoPaymentDate">Pagamento do boleto</Label>
|
||||
<DatePicker
|
||||
id="boletoPaymentDate"
|
||||
value={formState.boletoPaymentDate}
|
||||
onChange={(value) => onFieldChange("boletoPaymentDate", value)}
|
||||
placeholder="Selecione a data de pagamento"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
"use client";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { LANCAMENTO_TRANSACTION_TYPES } from "@/lib/lancamentos/constants";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import {
|
||||
CategoriaSelectContent,
|
||||
TransactionTypeSelectContent,
|
||||
} from "../../select-items";
|
||||
import type { CategorySectionProps } from "./lancamento-dialog-types";
|
||||
|
||||
export function CategorySection({
|
||||
formState,
|
||||
onFieldChange,
|
||||
categoriaOptions,
|
||||
categoriaGroups,
|
||||
isUpdateMode,
|
||||
}: CategorySectionProps) {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||
{!isUpdateMode ? (
|
||||
<div className="w-full space-y-1 md:w-1/2">
|
||||
<Label htmlFor="transactionType">Tipo de transação</Label>
|
||||
<Select
|
||||
value={formState.transactionType}
|
||||
onValueChange={(value) => onFieldChange("transactionType", value)}
|
||||
>
|
||||
<SelectTrigger id="transactionType" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.transactionType && (
|
||||
<TransactionTypeSelectContent
|
||||
label={formState.transactionType}
|
||||
/>
|
||||
)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LANCAMENTO_TRANSACTION_TYPES.filter(
|
||||
(type) => type !== "Transferência"
|
||||
).map((type) => (
|
||||
<SelectItem key={type} value={type}>
|
||||
<TransactionTypeSelectContent label={type} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-1 w-full",
|
||||
!isUpdateMode ? "md:w-1/2" : "md:w-full"
|
||||
)}
|
||||
>
|
||||
<Label htmlFor="categoria">Categoria</Label>
|
||||
<Select
|
||||
value={formState.categoriaId}
|
||||
onValueChange={(value) => onFieldChange("categoriaId", value)}
|
||||
>
|
||||
<SelectTrigger id="categoria" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.categoriaId &&
|
||||
(() => {
|
||||
const selectedOption = categoriaOptions.find(
|
||||
(opt) => opt.value === formState.categoriaId
|
||||
);
|
||||
return selectedOption ? (
|
||||
<CategoriaSelectContent
|
||||
label={selectedOption.label}
|
||||
icon={selectedOption.icon}
|
||||
/>
|
||||
) : null;
|
||||
})()}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{categoriaGroups.map((group) => (
|
||||
<SelectGroup key={group.label}>
|
||||
<SelectLabel>{group.label}</SelectLabel>
|
||||
{group.options.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<CategoriaSelectContent
|
||||
label={option.label}
|
||||
icon={option.icon}
|
||||
/>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { LANCAMENTO_CONDITIONS } from "@/lib/lancamentos/constants";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import { ConditionSelectContent } from "../../select-items";
|
||||
import type { ConditionSectionProps } from "./lancamento-dialog-types";
|
||||
|
||||
export function ConditionSection({
|
||||
formState,
|
||||
onFieldChange,
|
||||
showInstallments,
|
||||
showRecurrence,
|
||||
}: ConditionSectionProps) {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-2 w-full",
|
||||
showInstallments || showRecurrence ? "md:w-1/2" : "md:w-full"
|
||||
)}
|
||||
>
|
||||
<Label htmlFor="condition">Condição</Label>
|
||||
<Select
|
||||
value={formState.condition}
|
||||
onValueChange={(value) => onFieldChange("condition", value)}
|
||||
>
|
||||
<SelectTrigger id="condition" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.condition && (
|
||||
<ConditionSelectContent label={formState.condition} />
|
||||
)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LANCAMENTO_CONDITIONS.map((condition) => (
|
||||
<SelectItem key={condition} value={condition}>
|
||||
<ConditionSelectContent label={condition} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{showInstallments ? (
|
||||
<div className="space-y-2 w-full md:w-1/2">
|
||||
<Label htmlFor="installmentCount">Parcelado em</Label>
|
||||
<Select
|
||||
value={formState.installmentCount}
|
||||
onValueChange={(value) => onFieldChange("installmentCount", value)}
|
||||
>
|
||||
<SelectTrigger id="installmentCount" className="w-full">
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{[...Array(24)].map((_, index) => (
|
||||
<SelectItem key={index + 2} value={String(index + 2)}>
|
||||
{index + 2}x
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showRecurrence ? (
|
||||
<div className="space-y-2 w-full md:w-1/2">
|
||||
<Label htmlFor="recurrenceCount">Lançamento fixo</Label>
|
||||
<Select
|
||||
value={formState.recurrenceCount}
|
||||
onValueChange={(value) => onFieldChange("recurrenceCount", value)}
|
||||
>
|
||||
<SelectTrigger id="recurrenceCount" className="w-full">
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{[...Array(24)].map((_, index) => (
|
||||
<SelectItem key={index + 2} value={String(index + 2)}>
|
||||
Por {index + 2} meses
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { LancamentoFormState } from "@/lib/lancamentos/form-helpers";
|
||||
import type { LancamentoItem, SelectOption } from "../../types";
|
||||
|
||||
export type FormState = LancamentoFormState;
|
||||
|
||||
export interface LancamentoDialogProps {
|
||||
mode: "create" | "update";
|
||||
trigger?: React.ReactNode;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
pagadorOptions: SelectOption[];
|
||||
splitPagadorOptions: SelectOption[];
|
||||
defaultPagadorId?: string | null;
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
categoriaOptions: SelectOption[];
|
||||
estabelecimentos: string[];
|
||||
lancamento?: LancamentoItem;
|
||||
defaultPeriod?: string;
|
||||
defaultCartaoId?: string | null;
|
||||
defaultPaymentMethod?: string | null;
|
||||
defaultPurchaseDate?: string | null;
|
||||
lockCartaoSelection?: boolean;
|
||||
lockPaymentMethod?: boolean;
|
||||
onBulkEditRequest?: (data: {
|
||||
id: string;
|
||||
name: string;
|
||||
categoriaId: string | undefined;
|
||||
note: string;
|
||||
pagadorId: string | undefined;
|
||||
contaId: string | undefined;
|
||||
cartaoId: string | undefined;
|
||||
amount: number;
|
||||
dueDate: string | null;
|
||||
boletoPaymentDate: string | null;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export interface BaseFieldSectionProps {
|
||||
formState: FormState;
|
||||
onFieldChange: <Key extends keyof FormState>(
|
||||
key: Key,
|
||||
value: FormState[Key]
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface BasicFieldsSectionProps extends BaseFieldSectionProps {
|
||||
estabelecimentos: string[];
|
||||
monthOptions: Array<{ value: string; label: string }>;
|
||||
}
|
||||
|
||||
export interface CategorySectionProps extends BaseFieldSectionProps {
|
||||
categoriaOptions: SelectOption[];
|
||||
categoriaGroups: Array<{
|
||||
label: string;
|
||||
options: SelectOption[];
|
||||
}>;
|
||||
isUpdateMode: boolean;
|
||||
}
|
||||
|
||||
export interface SplitAndSettlementSectionProps extends BaseFieldSectionProps {
|
||||
showSettledToggle: boolean;
|
||||
}
|
||||
|
||||
export interface PagadorSectionProps extends BaseFieldSectionProps {
|
||||
pagadorOptions: SelectOption[];
|
||||
secondaryPagadorOptions: SelectOption[];
|
||||
}
|
||||
|
||||
export interface PaymentMethodSectionProps extends BaseFieldSectionProps {
|
||||
contaOptions: SelectOption[];
|
||||
cartaoOptions: SelectOption[];
|
||||
isUpdateMode: boolean;
|
||||
disablePaymentMethod: boolean;
|
||||
disableCartaoSelect: boolean;
|
||||
}
|
||||
|
||||
export interface BoletoFieldsSectionProps extends BaseFieldSectionProps {
|
||||
showPaymentDate: boolean;
|
||||
}
|
||||
|
||||
export interface ConditionSectionProps extends BaseFieldSectionProps {
|
||||
showInstallments: boolean;
|
||||
showRecurrence: boolean;
|
||||
}
|
||||
|
||||
export type NoteSectionProps = BaseFieldSectionProps;
|
||||
@@ -0,0 +1,421 @@
|
||||
"use client";
|
||||
import {
|
||||
createLancamentoAction,
|
||||
updateLancamentoAction,
|
||||
} from "@/app/(dashboard)/lancamentos/actions";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useControlledState } from "@/hooks/use-controlled-state";
|
||||
import {
|
||||
filterSecondaryPagadorOptions,
|
||||
groupAndSortCategorias,
|
||||
} from "@/lib/lancamentos/categoria-helpers";
|
||||
import {
|
||||
applyFieldDependencies,
|
||||
buildLancamentoInitialState,
|
||||
} from "@/lib/lancamentos/form-helpers";
|
||||
import { createMonthOptions } from "@/lib/utils/period";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useTransition,
|
||||
} from "react";
|
||||
import { toast } from "sonner";
|
||||
import { BasicFieldsSection } from "./basic-fields-section";
|
||||
import { BoletoFieldsSection } from "./boleto-fields-section";
|
||||
import { CategorySection } from "./category-section";
|
||||
import { ConditionSection } from "./condition-section";
|
||||
import type {
|
||||
FormState,
|
||||
LancamentoDialogProps,
|
||||
} from "./lancamento-dialog-types";
|
||||
import { NoteSection } from "./note-section";
|
||||
import { PagadorSection } from "./pagador-section";
|
||||
import { PaymentMethodSection } from "./payment-method-section";
|
||||
import { SplitAndSettlementSection } from "./split-settlement-section";
|
||||
|
||||
export function LancamentoDialog({
|
||||
mode,
|
||||
trigger,
|
||||
open,
|
||||
onOpenChange,
|
||||
pagadorOptions,
|
||||
splitPagadorOptions,
|
||||
defaultPagadorId,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
categoriaOptions,
|
||||
estabelecimentos,
|
||||
lancamento,
|
||||
defaultPeriod,
|
||||
defaultCartaoId,
|
||||
defaultPaymentMethod,
|
||||
defaultPurchaseDate,
|
||||
lockCartaoSelection,
|
||||
lockPaymentMethod,
|
||||
onBulkEditRequest,
|
||||
}: LancamentoDialogProps) {
|
||||
const [dialogOpen, setDialogOpen] = useControlledState(
|
||||
open,
|
||||
false,
|
||||
onOpenChange
|
||||
);
|
||||
|
||||
const [formState, setFormState] = useState<FormState>(() =>
|
||||
buildLancamentoInitialState(lancamento, defaultPagadorId, defaultPeriod, {
|
||||
defaultCartaoId,
|
||||
defaultPaymentMethod,
|
||||
defaultPurchaseDate,
|
||||
})
|
||||
);
|
||||
const [periodDirty, setPeriodDirty] = useState(false);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogOpen) {
|
||||
setFormState(
|
||||
buildLancamentoInitialState(
|
||||
lancamento,
|
||||
defaultPagadorId,
|
||||
defaultPeriod,
|
||||
{
|
||||
defaultCartaoId,
|
||||
defaultPaymentMethod,
|
||||
defaultPurchaseDate,
|
||||
}
|
||||
)
|
||||
);
|
||||
setErrorMessage(null);
|
||||
setPeriodDirty(false);
|
||||
}
|
||||
}, [
|
||||
dialogOpen,
|
||||
lancamento,
|
||||
defaultPagadorId,
|
||||
defaultPeriod,
|
||||
defaultCartaoId,
|
||||
defaultPaymentMethod,
|
||||
defaultPurchaseDate,
|
||||
]);
|
||||
|
||||
const primaryPagador = formState.pagadorId;
|
||||
|
||||
const secondaryPagadorOptions = useMemo(
|
||||
() => filterSecondaryPagadorOptions(splitPagadorOptions, primaryPagador),
|
||||
[splitPagadorOptions, primaryPagador]
|
||||
);
|
||||
|
||||
const categoriaGroups = useMemo(() => {
|
||||
const filtered = categoriaOptions.filter(
|
||||
(option) =>
|
||||
option.group?.toLowerCase() === formState.transactionType.toLowerCase()
|
||||
);
|
||||
return groupAndSortCategorias(filtered);
|
||||
}, [categoriaOptions, formState.transactionType]);
|
||||
|
||||
const monthOptions = useMemo(
|
||||
() => createMonthOptions(formState.period),
|
||||
[formState.period]
|
||||
);
|
||||
|
||||
const handleFieldChange = useCallback(
|
||||
<Key extends keyof FormState>(key: Key, value: FormState[Key]) => {
|
||||
if (key === "period") {
|
||||
setPeriodDirty(true);
|
||||
}
|
||||
|
||||
setFormState((prev) => {
|
||||
const dependencies = applyFieldDependencies(
|
||||
key,
|
||||
value,
|
||||
prev,
|
||||
periodDirty
|
||||
);
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[key]: value,
|
||||
...dependencies,
|
||||
};
|
||||
});
|
||||
},
|
||||
[periodDirty]
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setErrorMessage(null);
|
||||
|
||||
if (!formState.purchaseDate) {
|
||||
const message = "Informe a data da transação.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formState.name.trim()) {
|
||||
const message = "Informe a descrição do lançamento.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (formState.isSplit && !formState.pagadorId) {
|
||||
const message =
|
||||
"Selecione o pagador principal para dividir o lançamento.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (formState.isSplit && !formState.secondaryPagadorId) {
|
||||
const message =
|
||||
"Selecione o pagador secundário para dividir o lançamento.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const amountValue = Number(formState.amount);
|
||||
if (Number.isNaN(amountValue)) {
|
||||
const message = "Informe um valor válido.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const sanitizedAmount = Math.abs(amountValue);
|
||||
|
||||
const payload = {
|
||||
purchaseDate: formState.purchaseDate,
|
||||
period: formState.period,
|
||||
name: formState.name.trim(),
|
||||
transactionType: formState.transactionType,
|
||||
amount: sanitizedAmount,
|
||||
condition: formState.condition,
|
||||
paymentMethod: formState.paymentMethod,
|
||||
pagadorId: formState.pagadorId,
|
||||
secondaryPagadorId: formState.isSplit
|
||||
? formState.secondaryPagadorId
|
||||
: undefined,
|
||||
isSplit: formState.isSplit,
|
||||
contaId: formState.contaId,
|
||||
cartaoId: formState.cartaoId,
|
||||
categoriaId: formState.categoriaId,
|
||||
note: formState.note.trim() || undefined,
|
||||
isSettled:
|
||||
formState.paymentMethod === "Cartão de crédito"
|
||||
? null
|
||||
: Boolean(formState.isSettled),
|
||||
installmentCount:
|
||||
formState.condition === "Parcelado" && formState.installmentCount
|
||||
? Number(formState.installmentCount)
|
||||
: undefined,
|
||||
recurrenceCount:
|
||||
formState.condition === "Recorrente" && formState.recurrenceCount
|
||||
? Number(formState.recurrenceCount)
|
||||
: undefined,
|
||||
dueDate:
|
||||
formState.paymentMethod === "Boleto" && formState.dueDate
|
||||
? formState.dueDate
|
||||
: undefined,
|
||||
boletoPaymentDate:
|
||||
mode === "update" &&
|
||||
formState.paymentMethod === "Boleto" &&
|
||||
formState.boletoPaymentDate
|
||||
? formState.boletoPaymentDate
|
||||
: undefined,
|
||||
};
|
||||
|
||||
startTransition(async () => {
|
||||
if (mode === "create") {
|
||||
const result = await createLancamentoAction(payload);
|
||||
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
setDialogOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setErrorMessage(result.error);
|
||||
toast.error(result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update mode
|
||||
const hasSeriesId = Boolean(lancamento?.seriesId);
|
||||
|
||||
if (hasSeriesId && onBulkEditRequest) {
|
||||
// Para lançamentos em série, abre o diálogo de bulk action
|
||||
onBulkEditRequest({
|
||||
id: lancamento?.id ?? "",
|
||||
name: formState.name.trim(),
|
||||
categoriaId: formState.categoriaId,
|
||||
note: formState.note.trim() || "",
|
||||
pagadorId: formState.pagadorId,
|
||||
contaId: formState.contaId,
|
||||
cartaoId: formState.cartaoId,
|
||||
amount: sanitizedAmount,
|
||||
dueDate:
|
||||
formState.paymentMethod === "Boleto"
|
||||
? formState.dueDate || null
|
||||
: null,
|
||||
boletoPaymentDate:
|
||||
mode === "update" && formState.paymentMethod === "Boleto"
|
||||
? formState.boletoPaymentDate || null
|
||||
: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Atualização normal para lançamentos únicos ou todos os campos
|
||||
const result = await updateLancamentoAction({
|
||||
id: lancamento?.id ?? "",
|
||||
...payload,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
setDialogOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setErrorMessage(result.error);
|
||||
toast.error(result.error);
|
||||
});
|
||||
},
|
||||
[
|
||||
formState,
|
||||
mode,
|
||||
lancamento?.id,
|
||||
lancamento?.seriesId,
|
||||
setDialogOpen,
|
||||
onBulkEditRequest,
|
||||
]
|
||||
);
|
||||
|
||||
const title = mode === "create" ? "Novo lançamento" : "Editar lançamento";
|
||||
const description =
|
||||
mode === "create"
|
||||
? "Informe os dados abaixo para registrar um novo lançamento."
|
||||
: "Atualize as informações do lançamento selecionado.";
|
||||
const submitLabel = mode === "create" ? "Salvar lançamento" : "Atualizar";
|
||||
|
||||
const showInstallments = formState.condition === "Parcelado";
|
||||
const showRecurrence = formState.condition === "Recorrente";
|
||||
const showDueDate = formState.paymentMethod === "Boleto";
|
||||
const showPaymentDate = mode === "update" && showDueDate;
|
||||
const showSettledToggle = formState.paymentMethod !== "Cartão de crédito";
|
||||
const isUpdateMode = mode === "update";
|
||||
const disablePaymentMethod = Boolean(lockPaymentMethod && mode === "create");
|
||||
const disableCartaoSelect = Boolean(lockCartaoSelection && mode === "create");
|
||||
|
||||
return (
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
{trigger ? <DialogTrigger asChild>{trigger}</DialogTrigger> : null}
|
||||
<DialogContent className="sm:max-w-xl p-6 sm:px-8">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form
|
||||
className="space-y-2 -mx-6 max-h-[80vh] overflow-y-auto px-6 pb-1"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<BasicFieldsSection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
estabelecimentos={estabelecimentos}
|
||||
monthOptions={monthOptions}
|
||||
/>
|
||||
|
||||
<CategorySection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
categoriaOptions={categoriaOptions}
|
||||
categoriaGroups={categoriaGroups}
|
||||
isUpdateMode={isUpdateMode}
|
||||
/>
|
||||
|
||||
{!isUpdateMode ? (
|
||||
<SplitAndSettlementSection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
showSettledToggle={showSettledToggle}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<PagadorSection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
pagadorOptions={pagadorOptions}
|
||||
secondaryPagadorOptions={secondaryPagadorOptions}
|
||||
/>
|
||||
|
||||
<PaymentMethodSection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
isUpdateMode={isUpdateMode}
|
||||
disablePaymentMethod={disablePaymentMethod}
|
||||
disableCartaoSelect={disableCartaoSelect}
|
||||
/>
|
||||
|
||||
{showDueDate ? (
|
||||
<BoletoFieldsSection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
showPaymentDate={showPaymentDate}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!isUpdateMode ? (
|
||||
<ConditionSection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
showInstallments={showInstallments}
|
||||
showRecurrence={showRecurrence}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<NoteSection
|
||||
formState={formState}
|
||||
onFieldChange={handleFieldChange}
|
||||
/>
|
||||
|
||||
{errorMessage ? (
|
||||
<p className="text-sm text-destructive">{errorMessage}</p>
|
||||
) : null}
|
||||
|
||||
<DialogFooter className="gap-3">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setDialogOpen(false)}
|
||||
disabled={isPending}
|
||||
>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={isPending}>
|
||||
{isPending ? "Salvando..." : submitLabel}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import type { NoteSectionProps } from "./lancamento-dialog-types";
|
||||
|
||||
export function NoteSection({ formState, onFieldChange }: NoteSectionProps) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="note">Anotação</Label>
|
||||
<Textarea
|
||||
id="note"
|
||||
value={formState.note}
|
||||
onChange={(event) => onFieldChange("note", event.target.value)}
|
||||
placeholder="Adicione observações sobre o lançamento"
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { PagadorSelectContent } from "../../select-items";
|
||||
import type { PagadorSectionProps } from "./lancamento-dialog-types";
|
||||
|
||||
export function PagadorSection({
|
||||
formState,
|
||||
onFieldChange,
|
||||
pagadorOptions,
|
||||
secondaryPagadorOptions,
|
||||
}: PagadorSectionProps) {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||
<div className="w-full space-y-1">
|
||||
<Label htmlFor="pagador">Pagador</Label>
|
||||
<Select
|
||||
value={formState.pagadorId}
|
||||
onValueChange={(value) => onFieldChange("pagadorId", value)}
|
||||
>
|
||||
<SelectTrigger id="pagador" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.pagadorId &&
|
||||
(() => {
|
||||
const selectedOption = pagadorOptions.find(
|
||||
(opt) => opt.value === formState.pagadorId
|
||||
);
|
||||
return selectedOption ? (
|
||||
<PagadorSelectContent
|
||||
label={selectedOption.label}
|
||||
avatarUrl={selectedOption.avatarUrl}
|
||||
/>
|
||||
) : null;
|
||||
})()}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{pagadorOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<PagadorSelectContent
|
||||
label={option.label}
|
||||
avatarUrl={option.avatarUrl}
|
||||
/>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{formState.isSplit ? (
|
||||
<div className="w-full space-y-1">
|
||||
<Label htmlFor="secondaryPagador">Dividir com</Label>
|
||||
<Select
|
||||
value={formState.secondaryPagadorId}
|
||||
onValueChange={(value) =>
|
||||
onFieldChange("secondaryPagadorId", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="secondaryPagador"
|
||||
disabled={secondaryPagadorOptions.length === 0}
|
||||
className={"w-full"}
|
||||
>
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.secondaryPagadorId &&
|
||||
(() => {
|
||||
const selectedOption = secondaryPagadorOptions.find(
|
||||
(opt) => opt.value === formState.secondaryPagadorId
|
||||
);
|
||||
return selectedOption ? (
|
||||
<PagadorSelectContent
|
||||
label={selectedOption.label}
|
||||
avatarUrl={selectedOption.avatarUrl}
|
||||
/>
|
||||
) : null;
|
||||
})()}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{secondaryPagadorOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<PagadorSelectContent
|
||||
label={option.label}
|
||||
avatarUrl={option.avatarUrl}
|
||||
/>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
"use client";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { LANCAMENTO_PAYMENT_METHODS } from "@/lib/lancamentos/constants";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import {
|
||||
PaymentMethodSelectContent,
|
||||
ContaCartaoSelectContent,
|
||||
} from "../../select-items";
|
||||
import type { PaymentMethodSectionProps } from "./lancamento-dialog-types";
|
||||
|
||||
export function PaymentMethodSection({
|
||||
formState,
|
||||
onFieldChange,
|
||||
contaOptions,
|
||||
cartaoOptions,
|
||||
isUpdateMode,
|
||||
disablePaymentMethod,
|
||||
disableCartaoSelect,
|
||||
}: PaymentMethodSectionProps) {
|
||||
const isCartaoSelected = formState.paymentMethod === "Cartão de crédito";
|
||||
const showContaSelect = [
|
||||
"Pix",
|
||||
"Dinheiro",
|
||||
"Boleto",
|
||||
"Cartão de débito",
|
||||
].includes(formState.paymentMethod);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isUpdateMode ? (
|
||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-1 w-full",
|
||||
isCartaoSelected || showContaSelect ? "md:w-1/2" : "md:w-full"
|
||||
)}
|
||||
>
|
||||
<Label htmlFor="paymentMethod">Forma de pagamento</Label>
|
||||
<Select
|
||||
value={formState.paymentMethod}
|
||||
onValueChange={(value) => onFieldChange("paymentMethod", value)}
|
||||
disabled={disablePaymentMethod}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="paymentMethod"
|
||||
className="w-full"
|
||||
disabled={disablePaymentMethod}
|
||||
>
|
||||
<SelectValue placeholder="Selecione" className="w-full">
|
||||
{formState.paymentMethod && (
|
||||
<PaymentMethodSelectContent label={formState.paymentMethod} />
|
||||
)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LANCAMENTO_PAYMENT_METHODS.map((method) => (
|
||||
<SelectItem key={method} value={method}>
|
||||
<PaymentMethodSelectContent label={method} />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{isCartaoSelected ? (
|
||||
<div className="space-y-1 w-full md:w-1/2">
|
||||
<Label htmlFor="cartao">Cartão</Label>
|
||||
<Select
|
||||
value={formState.cartaoId}
|
||||
onValueChange={(value) => onFieldChange("cartaoId", value)}
|
||||
disabled={disableCartaoSelect}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="cartao"
|
||||
className="w-full"
|
||||
disabled={disableCartaoSelect}
|
||||
>
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.cartaoId &&
|
||||
(() => {
|
||||
const selectedOption = cartaoOptions.find(
|
||||
(opt) => opt.value === formState.cartaoId
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={true}
|
||||
/>
|
||||
) : null;
|
||||
})()}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{cartaoOptions.length === 0 ? (
|
||||
<div className="px-2 py-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Nenhum cartão cadastrado
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
cartaoOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={true}
|
||||
/>
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!isCartaoSelected && showContaSelect ? (
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-1 w-full",
|
||||
!isUpdateMode ? "md:w-1/2" : "md:w-full"
|
||||
)}
|
||||
>
|
||||
<Label htmlFor="conta">Conta</Label>
|
||||
<Select
|
||||
value={formState.contaId}
|
||||
onValueChange={(value) => onFieldChange("contaId", value)}
|
||||
>
|
||||
<SelectTrigger id="conta" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.contaId &&
|
||||
(() => {
|
||||
const selectedOption = contaOptions.find(
|
||||
(opt) => opt.value === formState.contaId
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={false}
|
||||
/>
|
||||
) : null;
|
||||
})()}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{contaOptions.length === 0 ? (
|
||||
<div className="px-2 py-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Nenhuma conta cadastrada
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
contaOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={false}
|
||||
/>
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isUpdateMode ? (
|
||||
<div className="flex w-full flex-col gap-2 md:flex-row">
|
||||
{isCartaoSelected ? (
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-1 w-full",
|
||||
!isUpdateMode ? "md:w-1/2" : "md:w-full"
|
||||
)}
|
||||
>
|
||||
<Label htmlFor="cartaoUpdate">Cartão</Label>
|
||||
<Select
|
||||
value={formState.cartaoId}
|
||||
onValueChange={(value) => onFieldChange("cartaoId", value)}
|
||||
>
|
||||
<SelectTrigger id="cartaoUpdate" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.cartaoId &&
|
||||
(() => {
|
||||
const selectedOption = cartaoOptions.find(
|
||||
(opt) => opt.value === formState.cartaoId
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={true}
|
||||
/>
|
||||
) : null;
|
||||
})()}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{cartaoOptions.length === 0 ? (
|
||||
<div className="px-2 py-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Nenhum cartão cadastrado
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
cartaoOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={true}
|
||||
/>
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!isCartaoSelected && showContaSelect ? (
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-1 w-full",
|
||||
!isUpdateMode ? "md:w-1/2" : "md:w-full"
|
||||
)}
|
||||
>
|
||||
<Label htmlFor="contaUpdate">Conta</Label>
|
||||
<Select
|
||||
value={formState.contaId}
|
||||
onValueChange={(value) => onFieldChange("contaId", value)}
|
||||
>
|
||||
<SelectTrigger id="contaUpdate" className="w-full">
|
||||
<SelectValue placeholder="Selecione">
|
||||
{formState.contaId &&
|
||||
(() => {
|
||||
const selectedOption = contaOptions.find(
|
||||
(opt) => opt.value === formState.contaId
|
||||
);
|
||||
return selectedOption ? (
|
||||
<ContaCartaoSelectContent
|
||||
label={selectedOption.label}
|
||||
logo={selectedOption.logo}
|
||||
isCartao={false}
|
||||
/>
|
||||
) : null;
|
||||
})()}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{contaOptions.length === 0 ? (
|
||||
<div className="px-2 py-6 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Nenhuma conta cadastrada
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
contaOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<ContaCartaoSelectContent
|
||||
label={option.label}
|
||||
logo={option.logo}
|
||||
isCartao={false}
|
||||
/>
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import type { SplitAndSettlementSectionProps } from "./lancamento-dialog-types";
|
||||
|
||||
export function SplitAndSettlementSection({
|
||||
formState,
|
||||
onFieldChange,
|
||||
showSettledToggle,
|
||||
}: SplitAndSettlementSectionProps) {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-2 py-2 md:flex-row">
|
||||
<div
|
||||
className={cn(
|
||||
"space-y-1",
|
||||
showSettledToggle ? "md:w-1/2 md:pr-2" : "md:w-full"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-foreground">Dividir lançamento</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Selecione para atribuir parte do valor a outro pagador.
|
||||
</p>
|
||||
</div>
|
||||
<Checkbox
|
||||
checked={formState.isSplit}
|
||||
onCheckedChange={(checked) =>
|
||||
onFieldChange("isSplit", Boolean(checked))
|
||||
}
|
||||
aria-label="Dividir lançamento"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showSettledToggle ? (
|
||||
<div className="space-y-1 md:w-1/2 md:pr-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-foreground">Marcar como pago</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Indica que o lançamento já foi pago ou recebido.
|
||||
</p>
|
||||
</div>
|
||||
<Checkbox
|
||||
checked={Boolean(formState.isSettled)}
|
||||
onCheckedChange={(checked) =>
|
||||
onFieldChange("isSettled", Boolean(checked))
|
||||
}
|
||||
aria-label="Marcar como concluído"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user