ajuste de layout mobile, melhorias e criação de novas funções. Detalhes adicionados no CHANGELOG.md

This commit is contained in:
Guilherme Bano
2026-02-18 23:21:14 -03:00
committed by Felipe Coutinho
parent 31fe752b7d
commit ffde55f589
29 changed files with 857 additions and 213 deletions

View File

@@ -1,7 +1,17 @@
import Link from "next/link";
import { Badge } from "@/components/ui/badge";
import { Card } from "@/components/ui/card";
import type { ChangelogVersion } from "@/lib/changelog/parse-changelog";
/** Converte "[texto](url)" em link; texto simples fica como está */
function parseContributorLine(content: string) {
const linkMatch = content.match(/^\[([^\]]+)\]\((https?:\/\/[^)]+)\)$/);
if (linkMatch) {
return { label: linkMatch[1], url: linkMatch[2] };
}
return { label: content, url: null };
}
const sectionBadgeVariant: Record<
string,
"success" | "info" | "destructive" | "secondary"
@@ -46,6 +56,29 @@ export function ChangelogTab({ versions }: { versions: ChangelogVersion[] }) {
</ul>
</div>
))}
{version.contributor && (
<div className="border-t pt-4 mt-4">
<span className="text-sm text-muted-foreground">
Contribuições:{" "}
{(() => {
const { label, url } = parseContributorLine(version.contributor);
if (url) {
return (
<Link
href={url}
target="_blank"
rel="noopener noreferrer"
className="font-medium text-foreground underline underline-offset-2 hover:text-primary"
>
{label}
</Link>
);
}
return <span className="font-medium text-foreground">{label}</span>;
})()}
</span>
</div>
)}
</div>
</Card>
))}

View File

@@ -1,5 +1,17 @@
"use client";
import {
DndContext,
closestCenter,
type DragEndEvent,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { RiDragMove2Line } from "@remixicon/react";
import { useRouter } from "next/navigation";
import { useEffect, useState, useTransition } from "react";
import { toast } from "sonner";
@@ -15,16 +27,58 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import {
DEFAULT_LANCAMENTOS_COLUMN_ORDER,
LANCAMENTOS_COLUMN_LABELS,
} from "@/lib/lancamentos/column-order";
import { FONT_OPTIONS, getFontVariable } from "@/public/fonts/font_index";
interface PreferencesFormProps {
disableMagnetlines: boolean;
extratoNoteAsColumn: boolean;
lancamentosColumnOrder: string[] | null;
systemFont: string;
moneyFont: string;
}
function SortableColumnItem({ id }: { id: string }) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
const label = LANCAMENTOS_COLUMN_LABELS[id] ?? id;
return (
<div
ref={setNodeRef}
style={style}
className={`flex cursor-grab active:cursor-grabbing items-center gap-2 rounded-md border bg-card px-3 py-2 text-sm touch-none select-none ${
isDragging ? "z-10 opacity-90 shadow-md" : ""
}`}
aria-label={`Arrastar ${label}`}
{...attributes}
{...listeners}
>
<RiDragMove2Line className="size-4 shrink-0 text-muted-foreground" aria-hidden />
<span>{label}</span>
</div>
);
}
export function PreferencesForm({
disableMagnetlines,
extratoNoteAsColumn: initialExtratoNoteAsColumn,
lancamentosColumnOrder: initialColumnOrder,
systemFont: initialSystemFont,
moneyFont: initialMoneyFont,
}: PreferencesFormProps) {
@@ -32,10 +86,33 @@ export function PreferencesForm({
const [isPending, startTransition] = useTransition();
const [magnetlinesDisabled, setMagnetlinesDisabled] =
useState(disableMagnetlines);
const [extratoNoteAsColumn, setExtratoNoteAsColumn] =
useState(initialExtratoNoteAsColumn);
const [columnOrder, setColumnOrder] = useState<string[]>(
initialColumnOrder && initialColumnOrder.length > 0
? initialColumnOrder
: DEFAULT_LANCAMENTOS_COLUMN_ORDER,
);
const [selectedSystemFont, setSelectedSystemFont] =
useState(initialSystemFont);
const [selectedMoneyFont, setSelectedMoneyFont] = useState(initialMoneyFont);
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
useSensor(KeyboardSensor),
);
const handleColumnDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (over && active.id !== over.id) {
setColumnOrder((items) => {
const oldIndex = items.indexOf(active.id as string);
const newIndex = items.indexOf(over.id as string);
return arrayMove(items, oldIndex, newIndex);
});
}
};
const fontCtx = useFont();
// Live preview: update CSS vars when font selection changes
@@ -53,6 +130,8 @@ export function PreferencesForm({
startTransition(async () => {
const result = await updatePreferencesAction({
disableMagnetlines: magnetlinesDisabled,
extratoNoteAsColumn,
lancamentosColumnOrder: columnOrder,
systemFont: selectedSystemFont,
moneyFont: selectedMoneyFont,
});
@@ -148,7 +227,59 @@ export function PreferencesForm({
<div className="border-b" />
{/* Seção 3: Dashboard */}
{/* Seção: Extrato / Lançamentos */}
<section className="space-y-4">
<div>
<h3 className="text-base font-semibold">Extrato e lançamentos</h3>
<p className="text-sm text-muted-foreground">
Como exibir anotações e a ordem das colunas na tabela de movimentações.
</p>
</div>
<div className="flex items-center justify-between rounded-lg border p-4 max-w-md">
<div className="space-y-0.5">
<Label htmlFor="extrato-note-column" className="text-base">
Anotações em coluna
</Label>
<p className="text-sm text-muted-foreground">
Quando ativo, as anotações aparecem em uma coluna na tabela. Quando desativado, aparecem em um balão ao passar o mouse no ícone.
</p>
</div>
<Switch
id="extrato-note-column"
checked={extratoNoteAsColumn}
onCheckedChange={setExtratoNoteAsColumn}
disabled={isPending}
/>
</div>
<div className="space-y-2 max-w-md">
<Label className="text-base">Ordem das colunas</Label>
<p className="text-sm text-muted-foreground">
Arraste os itens para definir a ordem em que as colunas aparecem na tabela do extrato e dos lançamentos.
</p>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleColumnDragEnd}
>
<SortableContext
items={columnOrder}
strategy={verticalListSortingStrategy}
>
<div className="flex flex-col gap-2 pt-2">
{columnOrder.map((id) => (
<SortableColumnItem key={id} id={id} />
))}
</div>
</SortableContext>
</DndContext>
</div>
</section>
<div className="border-b" />
{/* Seção: Dashboard */}
<section className="space-y-4">
<div>
<h3 className="text-base font-semibold">Dashboard</h3>