refactor: migrate from ESLint to Biome and extract SQL queries to data.ts
- Replace ESLint with Biome for linting and formatting - Configure Biome with tabs, double quotes, and organized imports - Move all SQL/Drizzle queries from page.tsx files to data.ts files - Create new data.ts files for: ajustes, dashboard, relatorios/categorias - Update existing data.ts files: extrato, fatura (add lancamentos queries) - Remove all drizzle-orm imports from page.tsx files - Update README.md with new tooling info Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,29 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
createAccountAction,
|
||||
updateAccountAction,
|
||||
} from "@/app/(dashboard)/contas/actions";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useTransition,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useTransition,
|
||||
} from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import {
|
||||
createAccountAction,
|
||||
updateAccountAction,
|
||||
} from "@/app/(dashboard)/contas/actions";
|
||||
import { LogoPickerDialog, LogoPickerTrigger } from "@/components/logo-picker";
|
||||
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 { useFormState } from "@/hooks/use-form-state";
|
||||
import { useLogoSelection } from "@/hooks/use-logo-selection";
|
||||
@@ -34,237 +33,237 @@ import { AccountFormFields } from "./account-form-fields";
|
||||
import type { Account, AccountFormValues } from "./types";
|
||||
|
||||
const DEFAULT_ACCOUNT_TYPES = [
|
||||
"Conta Corrente",
|
||||
"Conta Poupança",
|
||||
"Carteira Digital",
|
||||
"Conta Investimento",
|
||||
"Pré-Pago | VR/VA",
|
||||
"Conta Corrente",
|
||||
"Conta Poupança",
|
||||
"Carteira Digital",
|
||||
"Conta Investimento",
|
||||
"Pré-Pago | VR/VA",
|
||||
] as const;
|
||||
|
||||
const DEFAULT_ACCOUNT_STATUS = ["Ativa", "Inativa"] as const;
|
||||
|
||||
interface AccountDialogProps {
|
||||
mode: "create" | "update";
|
||||
trigger?: React.ReactNode;
|
||||
logoOptions: string[];
|
||||
account?: Account;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
mode: "create" | "update";
|
||||
trigger?: React.ReactNode;
|
||||
logoOptions: string[];
|
||||
account?: Account;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const buildInitialValues = ({
|
||||
account,
|
||||
logoOptions,
|
||||
accountTypes,
|
||||
accountStatuses,
|
||||
account,
|
||||
logoOptions,
|
||||
accountTypes,
|
||||
accountStatuses,
|
||||
}: {
|
||||
account?: Account;
|
||||
logoOptions: string[];
|
||||
accountTypes: string[];
|
||||
accountStatuses: string[];
|
||||
account?: Account;
|
||||
logoOptions: string[];
|
||||
accountTypes: string[];
|
||||
accountStatuses: string[];
|
||||
}): AccountFormValues => {
|
||||
const fallbackLogo = logoOptions[0] ?? "";
|
||||
const selectedLogo = normalizeLogo(account?.logo) || fallbackLogo;
|
||||
const derivedName = deriveNameFromLogo(selectedLogo);
|
||||
const fallbackLogo = logoOptions[0] ?? "";
|
||||
const selectedLogo = normalizeLogo(account?.logo) || fallbackLogo;
|
||||
const derivedName = deriveNameFromLogo(selectedLogo);
|
||||
|
||||
return {
|
||||
name: account?.name ?? derivedName,
|
||||
accountType: account?.accountType ?? accountTypes[0] ?? "",
|
||||
status: account?.status ?? accountStatuses[0] ?? "",
|
||||
note: account?.note ?? "",
|
||||
logo: selectedLogo,
|
||||
initialBalance: formatInitialBalanceInput(account?.initialBalance ?? 0),
|
||||
excludeFromBalance: account?.excludeFromBalance ?? false,
|
||||
excludeInitialBalanceFromIncome:
|
||||
account?.excludeInitialBalanceFromIncome ?? false,
|
||||
};
|
||||
return {
|
||||
name: account?.name ?? derivedName,
|
||||
accountType: account?.accountType ?? accountTypes[0] ?? "",
|
||||
status: account?.status ?? accountStatuses[0] ?? "",
|
||||
note: account?.note ?? "",
|
||||
logo: selectedLogo,
|
||||
initialBalance: formatInitialBalanceInput(account?.initialBalance ?? 0),
|
||||
excludeFromBalance: account?.excludeFromBalance ?? false,
|
||||
excludeInitialBalanceFromIncome:
|
||||
account?.excludeInitialBalanceFromIncome ?? false,
|
||||
};
|
||||
};
|
||||
|
||||
export function AccountDialog({
|
||||
mode,
|
||||
trigger,
|
||||
logoOptions,
|
||||
account,
|
||||
open,
|
||||
onOpenChange,
|
||||
mode,
|
||||
trigger,
|
||||
logoOptions,
|
||||
account,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: AccountDialogProps) {
|
||||
const [logoDialogOpen, setLogoDialogOpen] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [logoDialogOpen, setLogoDialogOpen] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
// Use controlled state hook for dialog open state
|
||||
const [dialogOpen, setDialogOpen] = useControlledState(
|
||||
open,
|
||||
false,
|
||||
onOpenChange
|
||||
);
|
||||
// Use controlled state hook for dialog open state
|
||||
const [dialogOpen, setDialogOpen] = useControlledState(
|
||||
open,
|
||||
false,
|
||||
onOpenChange,
|
||||
);
|
||||
|
||||
const accountTypes = useMemo(() => {
|
||||
const values = new Set<string>(DEFAULT_ACCOUNT_TYPES);
|
||||
if (account?.accountType) {
|
||||
values.add(account.accountType);
|
||||
}
|
||||
return Array.from(values);
|
||||
}, [account?.accountType]);
|
||||
const accountTypes = useMemo(() => {
|
||||
const values = new Set<string>(DEFAULT_ACCOUNT_TYPES);
|
||||
if (account?.accountType) {
|
||||
values.add(account.accountType);
|
||||
}
|
||||
return Array.from(values);
|
||||
}, [account?.accountType]);
|
||||
|
||||
const accountStatuses = useMemo(() => {
|
||||
const values = new Set<string>(DEFAULT_ACCOUNT_STATUS);
|
||||
if (account?.status) {
|
||||
values.add(account.status);
|
||||
}
|
||||
return Array.from(values);
|
||||
}, [account?.status]);
|
||||
const accountStatuses = useMemo(() => {
|
||||
const values = new Set<string>(DEFAULT_ACCOUNT_STATUS);
|
||||
if (account?.status) {
|
||||
values.add(account.status);
|
||||
}
|
||||
return Array.from(values);
|
||||
}, [account?.status]);
|
||||
|
||||
const initialState = useMemo(
|
||||
() =>
|
||||
buildInitialValues({
|
||||
account,
|
||||
logoOptions,
|
||||
accountTypes,
|
||||
accountStatuses,
|
||||
}),
|
||||
[account, logoOptions, accountTypes, accountStatuses]
|
||||
);
|
||||
const initialState = useMemo(
|
||||
() =>
|
||||
buildInitialValues({
|
||||
account,
|
||||
logoOptions,
|
||||
accountTypes,
|
||||
accountStatuses,
|
||||
}),
|
||||
[account, logoOptions, accountTypes, accountStatuses],
|
||||
);
|
||||
|
||||
// Use form state hook for form management
|
||||
const { formState, updateField, updateFields, setFormState } =
|
||||
useFormState<AccountFormValues>(initialState);
|
||||
// Use form state hook for form management
|
||||
const { formState, updateField, updateFields, setFormState } =
|
||||
useFormState<AccountFormValues>(initialState);
|
||||
|
||||
// Reset form when dialog opens
|
||||
useEffect(() => {
|
||||
if (dialogOpen) {
|
||||
setFormState(initialState);
|
||||
setErrorMessage(null);
|
||||
}
|
||||
}, [dialogOpen, initialState, setFormState]);
|
||||
// Reset form when dialog opens
|
||||
useEffect(() => {
|
||||
if (dialogOpen) {
|
||||
setFormState(initialState);
|
||||
setErrorMessage(null);
|
||||
}
|
||||
}, [dialogOpen, initialState, setFormState]);
|
||||
|
||||
// Close logo dialog when main dialog closes
|
||||
useEffect(() => {
|
||||
if (!dialogOpen) {
|
||||
setErrorMessage(null);
|
||||
setLogoDialogOpen(false);
|
||||
}
|
||||
}, [dialogOpen]);
|
||||
// Close logo dialog when main dialog closes
|
||||
useEffect(() => {
|
||||
if (!dialogOpen) {
|
||||
setErrorMessage(null);
|
||||
setLogoDialogOpen(false);
|
||||
}
|
||||
}, [dialogOpen]);
|
||||
|
||||
// Use logo selection hook
|
||||
const handleLogoSelection = useLogoSelection({
|
||||
mode,
|
||||
currentLogo: formState.logo,
|
||||
currentName: formState.name,
|
||||
onUpdate: (updates) => {
|
||||
updateFields(updates);
|
||||
setLogoDialogOpen(false);
|
||||
},
|
||||
});
|
||||
// Use logo selection hook
|
||||
const handleLogoSelection = useLogoSelection({
|
||||
mode,
|
||||
currentLogo: formState.logo,
|
||||
currentName: formState.name,
|
||||
onUpdate: (updates) => {
|
||||
updateFields(updates);
|
||||
setLogoDialogOpen(false);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setErrorMessage(null);
|
||||
const handleSubmit = useCallback(
|
||||
(event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setErrorMessage(null);
|
||||
|
||||
if (mode === "update" && !account?.id) {
|
||||
const message = "Conta inválida.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
if (mode === "update" && !account?.id) {
|
||||
const message = "Conta inválida.";
|
||||
setErrorMessage(message);
|
||||
toast.error(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = { ...formState };
|
||||
const payload = { ...formState };
|
||||
|
||||
if (!payload.logo) {
|
||||
setErrorMessage("Selecione um logo.");
|
||||
return;
|
||||
}
|
||||
if (!payload.logo) {
|
||||
setErrorMessage("Selecione um logo.");
|
||||
return;
|
||||
}
|
||||
|
||||
startTransition(async () => {
|
||||
const result =
|
||||
mode === "create"
|
||||
? await createAccountAction(payload)
|
||||
: await updateAccountAction({
|
||||
id: account?.id ?? "",
|
||||
...payload,
|
||||
});
|
||||
startTransition(async () => {
|
||||
const result =
|
||||
mode === "create"
|
||||
? await createAccountAction(payload)
|
||||
: await updateAccountAction({
|
||||
id: account?.id ?? "",
|
||||
...payload,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
setDialogOpen(false);
|
||||
setFormState(initialState);
|
||||
return;
|
||||
}
|
||||
if (result.success) {
|
||||
toast.success(result.message);
|
||||
setDialogOpen(false);
|
||||
setFormState(initialState);
|
||||
return;
|
||||
}
|
||||
|
||||
setErrorMessage(result.error);
|
||||
toast.error(result.error);
|
||||
});
|
||||
},
|
||||
[account?.id, formState, initialState, mode, setDialogOpen, setFormState]
|
||||
);
|
||||
setErrorMessage(result.error);
|
||||
toast.error(result.error);
|
||||
});
|
||||
},
|
||||
[account?.id, formState, initialState, mode, setDialogOpen, setFormState],
|
||||
);
|
||||
|
||||
const title = mode === "create" ? "Nova conta" : "Editar conta";
|
||||
const description =
|
||||
mode === "create"
|
||||
? "Cadastre uma nova conta para organizar seus lançamentos."
|
||||
: "Atualize as informações da conta selecionada.";
|
||||
const submitLabel = mode === "create" ? "Salvar conta" : "Atualizar conta";
|
||||
const title = mode === "create" ? "Nova conta" : "Editar conta";
|
||||
const description =
|
||||
mode === "create"
|
||||
? "Cadastre uma nova conta para organizar seus lançamentos."
|
||||
: "Atualize as informações da conta selecionada.";
|
||||
const submitLabel = mode === "create" ? "Salvar conta" : "Atualizar conta";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
{trigger ? <DialogTrigger asChild>{trigger}</DialogTrigger> : null}
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
return (
|
||||
<>
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
{trigger ? <DialogTrigger asChild>{trigger}</DialogTrigger> : null}
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form className="flex flex-col gap-5" onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<LogoPickerTrigger
|
||||
selectedLogo={formState.logo}
|
||||
disabled={logoOptions.length === 0}
|
||||
onOpen={() => {
|
||||
if (logoOptions.length > 0) {
|
||||
setLogoDialogOpen(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<form className="flex flex-col gap-5" onSubmit={handleSubmit}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<LogoPickerTrigger
|
||||
selectedLogo={formState.logo}
|
||||
disabled={logoOptions.length === 0}
|
||||
onOpen={() => {
|
||||
if (logoOptions.length > 0) {
|
||||
setLogoDialogOpen(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AccountFormFields
|
||||
values={formState}
|
||||
accountTypes={accountTypes}
|
||||
accountStatuses={accountStatuses}
|
||||
onChange={updateField}
|
||||
showInitialBalance={mode === "create"}
|
||||
/>
|
||||
<AccountFormFields
|
||||
values={formState}
|
||||
accountTypes={accountTypes}
|
||||
accountStatuses={accountStatuses}
|
||||
onChange={updateField}
|
||||
showInitialBalance={mode === "create"}
|
||||
/>
|
||||
|
||||
{errorMessage && (
|
||||
<p className="text-sm text-destructive">{errorMessage}</p>
|
||||
)}
|
||||
{errorMessage && (
|
||||
<p className="text-sm text-destructive">{errorMessage}</p>
|
||||
)}
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<LogoPickerDialog
|
||||
open={logoDialogOpen}
|
||||
logos={logoOptions}
|
||||
value={formState.logo}
|
||||
onOpenChange={setLogoDialogOpen}
|
||||
onSelect={handleLogoSelection}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
<LogoPickerDialog
|
||||
open={logoDialogOpen}
|
||||
logos={logoOptions}
|
||||
value={formState.logo}
|
||||
onOpenChange={setLogoDialogOpen}
|
||||
onSelect={handleLogoSelection}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user