mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
style: normalizar formatacao de importacao e suporte
This commit is contained in:
@@ -33,7 +33,8 @@ export function decodeAccountCard(value: string): {
|
||||
id: string;
|
||||
} | null {
|
||||
if (value.startsWith("card:")) return { type: "card", id: value.slice(5) };
|
||||
if (value.startsWith("account:")) return { type: "account", id: value.slice(8) };
|
||||
if (value.startsWith("account:"))
|
||||
return { type: "account", id: value.slice(8) };
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -65,7 +66,9 @@ export function GlobalFields({
|
||||
onBulkCategoryChange,
|
||||
}: GlobalFieldsProps) {
|
||||
const isCard = accountCardValue?.startsWith("card:") ?? false;
|
||||
const expenseCategories = categoryOptions.filter((o) => o.group === "despesa");
|
||||
const expenseCategories = categoryOptions.filter(
|
||||
(o) => o.group === "despesa",
|
||||
);
|
||||
const incomeCategories = categoryOptions.filter((o) => o.group === "receita");
|
||||
|
||||
return (
|
||||
@@ -131,7 +134,10 @@ export function GlobalFields({
|
||||
<SelectContent>
|
||||
{payerOptions.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>
|
||||
<PayerSelectContent label={opt.label} avatarUrl={opt.avatarUrl} />
|
||||
<PayerSelectContent
|
||||
label={opt.label}
|
||||
avatarUrl={opt.avatarUrl}
|
||||
/>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -150,7 +156,10 @@ export function GlobalFields({
|
||||
<SelectLabel>Despesa</SelectLabel>
|
||||
{expenseCategories.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>
|
||||
<CategorySelectContent label={opt.label} icon={opt.icon} />
|
||||
<CategorySelectContent
|
||||
label={opt.label}
|
||||
icon={opt.icon}
|
||||
/>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
@@ -163,7 +172,10 @@ export function GlobalFields({
|
||||
<SelectLabel>Receita</SelectLabel>
|
||||
{incomeCategories.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value}>
|
||||
<CategorySelectContent label={opt.label} icon={opt.icon} />
|
||||
<CategorySelectContent
|
||||
label={opt.label}
|
||||
icon={opt.icon}
|
||||
/>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
@@ -172,17 +184,17 @@ export function GlobalFields({
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{isCard && (
|
||||
<div className="flex min-w-44 flex-col gap-1.5">
|
||||
<Label>Fatura</Label>
|
||||
<PeriodPicker
|
||||
value={invoicePeriod ?? ""}
|
||||
onChange={(v) => onInvoicePeriodChange(v || null)}
|
||||
placeholder="Selecionar fatura…"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isCard && (
|
||||
<div className="flex min-w-44 flex-col gap-1.5">
|
||||
<Label>Fatura</Label>
|
||||
<PeriodPicker
|
||||
value={invoicePeriod ?? ""}
|
||||
onChange={(v) => onInvoicePeriodChange(v || null)}
|
||||
placeholder="Selecionar fatura…"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useMemo, useState, useTransition } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useTransition,
|
||||
} from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
fetchCategoryMappings,
|
||||
saveCategoryMappings,
|
||||
} from "@/features/transactions/actions/category-memory-action";
|
||||
import { normalizeDescriptionKey } from "@/features/transactions/lib/import-utils";
|
||||
import {
|
||||
checkDuplicateFitIds,
|
||||
deleteTransactionByFitId,
|
||||
@@ -27,6 +32,7 @@ import {
|
||||
} from "@/features/transactions/components/import/review-table";
|
||||
import { UploadZone } from "@/features/transactions/components/import/upload-zone";
|
||||
import type { SelectOption } from "@/features/transactions/components/types";
|
||||
import { normalizeDescriptionKey } from "@/features/transactions/lib/import-utils";
|
||||
import { Button } from "@/shared/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
@@ -82,7 +88,8 @@ export function ImportPage({
|
||||
...t,
|
||||
isDuplicate: t.externalId ? duplicates.has(t.externalId) : false,
|
||||
selected: t.externalId ? !duplicates.has(t.externalId) : true,
|
||||
categoryId: categoryMappings[normalizeDescriptionKey(t.description)] ?? null,
|
||||
categoryId:
|
||||
categoryMappings[normalizeDescriptionKey(t.description)] ?? null,
|
||||
})),
|
||||
);
|
||||
} finally {
|
||||
@@ -167,7 +174,9 @@ export function ImportPage({
|
||||
const handleImport = () => {
|
||||
if (!statement || !canImport) return;
|
||||
|
||||
const decoded = decodeAccountCard(accountCardValue!);
|
||||
const decoded = accountCardValue
|
||||
? decodeAccountCard(accountCardValue)
|
||||
: null;
|
||||
const cardId = decoded?.type === "card" ? decoded.id : null;
|
||||
const accountId = decoded?.type === "account" ? decoded.id : null;
|
||||
const paymentMethod =
|
||||
@@ -197,7 +206,10 @@ export function ImportPage({
|
||||
|
||||
// Salva mapeamentos description → category (fire-and-forget)
|
||||
saveCategoryMappings(
|
||||
selectedRows.map((r) => ({ description: r.description, categoryId: r.categoryId })),
|
||||
selectedRows.map((r) => ({
|
||||
description: r.description,
|
||||
categoryId: r.categoryId,
|
||||
})),
|
||||
);
|
||||
|
||||
const { importBatchId } = result;
|
||||
@@ -236,7 +248,8 @@ export function ImportPage({
|
||||
<div>
|
||||
<CardTitle>Importar extrato</CardTitle>
|
||||
<CardDescription>
|
||||
Importe transações a partir de um arquivo .ofx ou planilha .xlsx exportado pelo seu banco.
|
||||
Importe transações a partir de um arquivo .ofx ou planilha .xlsx
|
||||
exportado pelo seu banco.
|
||||
</CardDescription>
|
||||
</div>
|
||||
<ImportSteps current={currentStep} />
|
||||
|
||||
@@ -34,7 +34,9 @@ export function ImportSteps({ current }: ImportStepsProps) {
|
||||
isCompleted &&
|
||||
"border-primary bg-primary text-primary-foreground",
|
||||
isActive && "border-primary text-primary",
|
||||
!isCompleted && !isActive && "border-muted-foreground/30 text-muted-foreground",
|
||||
!isCompleted &&
|
||||
!isActive &&
|
||||
"border-muted-foreground/30 text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{isCompleted ? (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useRef } from "react";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { useRef } from "react";
|
||||
import { CategorySelectContent } from "@/features/transactions/components/select-items";
|
||||
import type { SelectOption } from "@/features/transactions/components/types";
|
||||
import MoneyValues from "@/shared/components/money-values";
|
||||
@@ -91,9 +91,7 @@ export function ReviewTable({
|
||||
onCheckedChange={(v) => onToggleAll(!!v)}
|
||||
aria-label="Selecionar todas"
|
||||
data-state={
|
||||
!allSelected && someSelected
|
||||
? "indeterminate"
|
||||
: undefined
|
||||
!allSelected && someSelected ? "indeterminate" : undefined
|
||||
}
|
||||
/>
|
||||
</TableHead>
|
||||
@@ -114,7 +112,10 @@ export function ReviewTable({
|
||||
</TableRow>
|
||||
)}
|
||||
{virtualRows.map((virtualRow) => {
|
||||
const row = rows[virtualRow.index]!;
|
||||
const row = rows[virtualRow.index];
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
const index = virtualRow.index;
|
||||
return (
|
||||
<TableRow
|
||||
@@ -199,9 +200,7 @@ export function ReviewTable({
|
||||
<TableCell>
|
||||
<TransactionTypeBadge
|
||||
kind={
|
||||
row.transactionType === "income"
|
||||
? "Receita"
|
||||
: "Despesa"
|
||||
row.transactionType === "income" ? "Receita" : "Despesa"
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
@@ -37,7 +37,9 @@ export function UploadZone({ onParsed }: UploadZoneProps) {
|
||||
}
|
||||
onParsed(statement);
|
||||
} catch {
|
||||
setError("Não foi possível ler o arquivo. Verifique se é um OFX válido.");
|
||||
setError(
|
||||
"Não foi possível ler o arquivo. Verifique se é um OFX válido.",
|
||||
);
|
||||
}
|
||||
};
|
||||
reader.readAsText(file, "windows-1252");
|
||||
@@ -119,11 +121,7 @@ export function UploadZone({ onParsed }: UploadZoneProps) {
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
{error ? (
|
||||
<p className="text-destructive text-sm">{error}</p>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
{error ? <p className="text-destructive text-sm">{error}</p> : <span />}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDownloadTemplate}
|
||||
|
||||
Reference in New Issue
Block a user