mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 19:01:47 +00:00
feat(anexos): página de galeria de comprovantes e documentos
Adiciona rota `/attachments` com visualização de todos os anexos do usuário em grade, visualização inline de imagem e PDF, navegação entre arquivos do mesmo lançamento e download direto. Inclui também: - API REST em `/api/attachments` para servir os arquivos - Actions `fetch-by-id` e `fetch-dialog-options` em transactions - Item "Anexos" adicionado à navbar - `formatBytes` extraído para `src/shared/utils/number.ts` - Migrations de banco atualizadas - Fix: uploads e remoções de anexo agora funcionam para todos os lançamentos, não apenas os pertencentes a séries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
38
src/app/(dashboard)/attachments/loading.tsx
Normal file
38
src/app/(dashboard)/attachments/loading.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Skeleton } from "@/shared/components/ui/skeleton";
|
||||
|
||||
export default function AnexosLoading() {
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
<div className="w-full space-y-6">
|
||||
{/* Header */}
|
||||
<Skeleton className="h-10 w-40 rounded-md bg-foreground/10" />
|
||||
|
||||
{/* Month navigation */}
|
||||
<Skeleton className="h-10 w-64 rounded-md bg-foreground/10" />
|
||||
|
||||
{/* Count */}
|
||||
<Skeleton className="h-4 w-20 rounded-md bg-foreground/10" />
|
||||
|
||||
{/* Grid */}
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex flex-col overflow-hidden rounded-lg border"
|
||||
>
|
||||
<Skeleton className="aspect-square w-full bg-foreground/10" />
|
||||
<div className="space-y-1.5 p-2.5">
|
||||
<Skeleton className="h-3 w-3/4 rounded bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-full rounded bg-foreground/10" />
|
||||
<div className="flex justify-between">
|
||||
<Skeleton className="h-3 w-16 rounded bg-foreground/10" />
|
||||
<Skeleton className="h-3 w-12 rounded bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
36
src/app/(dashboard)/attachments/page.tsx
Normal file
36
src/app/(dashboard)/attachments/page.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { connection } from "next/server";
|
||||
import { AttachmentsPage } from "@/features/attachments/components/attachments-page";
|
||||
import { fetchAttachmentsForPeriod } from "@/features/attachments/queries";
|
||||
import { getUserId } from "@/shared/lib/auth/server";
|
||||
import { parsePeriodParam } from "@/shared/utils/period";
|
||||
|
||||
type PageSearchParams = Promise<Record<string, string | string[] | undefined>>;
|
||||
|
||||
type PageProps = {
|
||||
searchParams?: PageSearchParams;
|
||||
};
|
||||
|
||||
const getSingleParam = (
|
||||
params: Record<string, string | string[] | undefined> | undefined,
|
||||
key: string,
|
||||
) => {
|
||||
const value = params?.[key];
|
||||
if (!value) return null;
|
||||
return Array.isArray(value) ? (value[0] ?? null) : value;
|
||||
};
|
||||
|
||||
export default async function Page({ searchParams }: PageProps) {
|
||||
await connection();
|
||||
const userId = await getUserId();
|
||||
const resolvedSearchParams = searchParams ? await searchParams : undefined;
|
||||
const periodoParam = getSingleParam(resolvedSearchParams, "periodo");
|
||||
const { period } = parsePeriodParam(periodoParam);
|
||||
|
||||
const attachments = await fetchAttachmentsForPeriod(userId, period);
|
||||
|
||||
return (
|
||||
<main className="flex flex-col gap-6">
|
||||
<AttachmentsPage attachments={attachments} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user