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,158 +1,158 @@
|
||||
"use client";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
||||
import {
|
||||
RiArchiveLine,
|
||||
RiCheckLine,
|
||||
RiDeleteBin5Line,
|
||||
RiEyeLine,
|
||||
RiInboxUnarchiveLine,
|
||||
RiPencilLine,
|
||||
RiArchiveLine,
|
||||
RiCheckLine,
|
||||
RiDeleteBin5Line,
|
||||
RiEyeLine,
|
||||
RiInboxUnarchiveLine,
|
||||
RiPencilLine,
|
||||
} from "@remixicon/react";
|
||||
import { useMemo } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
||||
import type { Note } from "./types";
|
||||
|
||||
const DATE_FORMATTER = new Intl.DateTimeFormat("pt-BR", {
|
||||
dateStyle: "medium",
|
||||
dateStyle: "medium",
|
||||
});
|
||||
|
||||
interface NoteCardProps {
|
||||
note: Note;
|
||||
onEdit?: (note: Note) => void;
|
||||
onDetails?: (note: Note) => void;
|
||||
onRemove?: (note: Note) => void;
|
||||
onArquivar?: (note: Note) => void;
|
||||
isArquivadas?: boolean;
|
||||
note: Note;
|
||||
onEdit?: (note: Note) => void;
|
||||
onDetails?: (note: Note) => void;
|
||||
onRemove?: (note: Note) => void;
|
||||
onArquivar?: (note: Note) => void;
|
||||
isArquivadas?: boolean;
|
||||
}
|
||||
|
||||
export function NoteCard({
|
||||
note,
|
||||
onEdit,
|
||||
onDetails,
|
||||
onRemove,
|
||||
onArquivar,
|
||||
isArquivadas = false,
|
||||
note,
|
||||
onEdit,
|
||||
onDetails,
|
||||
onRemove,
|
||||
onArquivar,
|
||||
isArquivadas = false,
|
||||
}: NoteCardProps) {
|
||||
const { formattedDate, displayTitle } = useMemo(() => {
|
||||
const resolvedTitle = note.title.trim().length
|
||||
? note.title
|
||||
: "Anotação sem título";
|
||||
const { formattedDate, displayTitle } = useMemo(() => {
|
||||
const resolvedTitle = note.title.trim().length
|
||||
? note.title
|
||||
: "Anotação sem título";
|
||||
|
||||
return {
|
||||
displayTitle: resolvedTitle,
|
||||
formattedDate: DATE_FORMATTER.format(new Date(note.createdAt)),
|
||||
};
|
||||
}, [note.createdAt, note.title]);
|
||||
return {
|
||||
displayTitle: resolvedTitle,
|
||||
formattedDate: DATE_FORMATTER.format(new Date(note.createdAt)),
|
||||
};
|
||||
}, [note.createdAt, note.title]);
|
||||
|
||||
const isTask = note.type === "tarefa";
|
||||
const tasks = note.tasks || [];
|
||||
const completedCount = tasks.filter((t) => t.completed).length;
|
||||
const totalCount = tasks.length;
|
||||
const isTask = note.type === "tarefa";
|
||||
const tasks = note.tasks || [];
|
||||
const completedCount = tasks.filter((t) => t.completed).length;
|
||||
const totalCount = tasks.length;
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: "editar",
|
||||
icon: <RiPencilLine className="size-4" aria-hidden />,
|
||||
onClick: onEdit,
|
||||
variant: "default" as const,
|
||||
},
|
||||
{
|
||||
label: "detalhes",
|
||||
icon: <RiEyeLine className="size-4" aria-hidden />,
|
||||
onClick: onDetails,
|
||||
variant: "default" as const,
|
||||
},
|
||||
{
|
||||
label: isArquivadas ? "desarquivar" : "arquivar",
|
||||
icon: isArquivadas ? (
|
||||
<RiInboxUnarchiveLine className="size-4" aria-hidden />
|
||||
) : (
|
||||
<RiArchiveLine className="size-4" aria-hidden />
|
||||
),
|
||||
onClick: onArquivar,
|
||||
variant: "default" as const,
|
||||
},
|
||||
{
|
||||
label: "remover",
|
||||
icon: <RiDeleteBin5Line className="size-4" aria-hidden />,
|
||||
onClick: onRemove,
|
||||
variant: "destructive" as const,
|
||||
},
|
||||
].filter((action) => typeof action.onClick === "function");
|
||||
const actions = [
|
||||
{
|
||||
label: "editar",
|
||||
icon: <RiPencilLine className="size-4" aria-hidden />,
|
||||
onClick: onEdit,
|
||||
variant: "default" as const,
|
||||
},
|
||||
{
|
||||
label: "detalhes",
|
||||
icon: <RiEyeLine className="size-4" aria-hidden />,
|
||||
onClick: onDetails,
|
||||
variant: "default" as const,
|
||||
},
|
||||
{
|
||||
label: isArquivadas ? "desarquivar" : "arquivar",
|
||||
icon: isArquivadas ? (
|
||||
<RiInboxUnarchiveLine className="size-4" aria-hidden />
|
||||
) : (
|
||||
<RiArchiveLine className="size-4" aria-hidden />
|
||||
),
|
||||
onClick: onArquivar,
|
||||
variant: "default" as const,
|
||||
},
|
||||
{
|
||||
label: "remover",
|
||||
icon: <RiDeleteBin5Line className="size-4" aria-hidden />,
|
||||
onClick: onRemove,
|
||||
variant: "destructive" as const,
|
||||
},
|
||||
].filter((action) => typeof action.onClick === "function");
|
||||
|
||||
return (
|
||||
<Card className="h-[300px] w-[440px] gap-0">
|
||||
<CardContent className="flex flex-1 flex-col gap-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-lg font-semibold leading-tight text-foreground wrap-break-word">
|
||||
{displayTitle}
|
||||
</h3>
|
||||
</div>
|
||||
{isTask && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{completedCount}/{totalCount} concluídas
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<Card className="h-[300px] w-[440px] gap-0">
|
||||
<CardContent className="flex flex-1 flex-col gap-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-lg font-semibold leading-tight text-foreground wrap-break-word">
|
||||
{displayTitle}
|
||||
</h3>
|
||||
</div>
|
||||
{isTask && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{completedCount}/{totalCount} concluídas
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isTask ? (
|
||||
<div className="flex-1 overflow-auto space-y-2 mt-2">
|
||||
{tasks.slice(0, 5).map((task) => (
|
||||
<div key={task.id} className="flex items-start gap-2 text-sm">
|
||||
<div
|
||||
className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border ${
|
||||
task.completed
|
||||
? "bg-green-600 border-green-600"
|
||||
: "border-input"
|
||||
}`}
|
||||
>
|
||||
{task.completed && (
|
||||
<RiCheckLine className="h-3 w-3 text-background" />
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
className={`leading-relaxed ${
|
||||
task.completed ? "text-muted-foreground" : "text-foreground"
|
||||
}`}
|
||||
>
|
||||
{task.text}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{tasks.length > 5 && (
|
||||
<p className="text-xs text-muted-foreground pl-5 py-1">
|
||||
+{tasks.length - 5}
|
||||
{tasks.length - 5 === 1 ? "tarefa" : "tarefas"}...
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="flex-1 overflow-auto whitespace-pre-line text-sm text-muted-foreground wrap-break-word leading-relaxed mt-2">
|
||||
{note.description}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
{isTask ? (
|
||||
<div className="flex-1 overflow-auto space-y-2 mt-2">
|
||||
{tasks.slice(0, 5).map((task) => (
|
||||
<div key={task.id} className="flex items-start gap-2 text-sm">
|
||||
<div
|
||||
className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border ${
|
||||
task.completed
|
||||
? "bg-green-600 border-green-600"
|
||||
: "border-input"
|
||||
}`}
|
||||
>
|
||||
{task.completed && (
|
||||
<RiCheckLine className="h-3 w-3 text-background" />
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
className={`leading-relaxed ${
|
||||
task.completed ? "text-muted-foreground" : "text-foreground"
|
||||
}`}
|
||||
>
|
||||
{task.text}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{tasks.length > 5 && (
|
||||
<p className="text-xs text-muted-foreground pl-5 py-1">
|
||||
+{tasks.length - 5}
|
||||
{tasks.length - 5 === 1 ? "tarefa" : "tarefas"}...
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="flex-1 overflow-auto whitespace-pre-line text-sm text-muted-foreground wrap-break-word leading-relaxed mt-2">
|
||||
{note.description}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
{actions.length > 0 ? (
|
||||
<CardFooter className="flex flex-wrap gap-3 px-6 pt-3 text-sm">
|
||||
{actions.map(({ label, icon, onClick, variant }) => (
|
||||
<button
|
||||
key={label}
|
||||
type="button"
|
||||
onClick={() => onClick?.(note)}
|
||||
className={`flex items-center gap-1 font-medium transition-opacity hover:opacity-80 ${
|
||||
variant === "destructive" ? "text-destructive" : "text-primary"
|
||||
}`}
|
||||
aria-label={`${label} anotação`}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</CardFooter>
|
||||
) : null}
|
||||
</Card>
|
||||
);
|
||||
{actions.length > 0 ? (
|
||||
<CardFooter className="flex flex-wrap gap-3 px-6 pt-3 text-sm">
|
||||
{actions.map(({ label, icon, onClick, variant }) => (
|
||||
<button
|
||||
key={label}
|
||||
type="button"
|
||||
onClick={() => onClick?.(note)}
|
||||
className={`flex items-center gap-1 font-medium transition-opacity hover:opacity-80 ${
|
||||
variant === "destructive" ? "text-destructive" : "text-primary"
|
||||
}`}
|
||||
aria-label={`${label} anotação`}
|
||||
>
|
||||
{icon}
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</CardFooter>
|
||||
) : null}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user