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:
@@ -1,14 +1,25 @@
|
|||||||
import { ImportPage } from "@/features/transactions/components/import/import-page";
|
import { ImportPage } from "@/features/transactions/components/import/import-page";
|
||||||
|
import {
|
||||||
|
buildOptionSets,
|
||||||
|
buildSluggedFilters,
|
||||||
|
} from "@/features/transactions/page-helpers";
|
||||||
import { fetchTransactionFilterSources } from "@/features/transactions/queries";
|
import { fetchTransactionFilterSources } from "@/features/transactions/queries";
|
||||||
import { buildOptionSets, buildSluggedFilters } from "@/features/transactions/page-helpers";
|
|
||||||
import { getUserId } from "@/shared/lib/auth/server";
|
import { getUserId } from "@/shared/lib/auth/server";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const userId = await getUserId();
|
const userId = await getUserId();
|
||||||
const filterSources = await fetchTransactionFilterSources(userId);
|
const filterSources = await fetchTransactionFilterSources(userId);
|
||||||
const sluggedFilters = buildSluggedFilters(filterSources);
|
const sluggedFilters = buildSluggedFilters(filterSources);
|
||||||
const { payerOptions, accountOptions, cardOptions, categoryOptions, defaultPayerId } =
|
const {
|
||||||
buildOptionSets({ ...sluggedFilters, payerRows: filterSources.payerRows });
|
payerOptions,
|
||||||
|
accountOptions,
|
||||||
|
cardOptions,
|
||||||
|
categoryOptions,
|
||||||
|
defaultPayerId,
|
||||||
|
} = buildOptionSets({
|
||||||
|
...sluggedFilters,
|
||||||
|
payerRows: filterSources.payerRows,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col gap-6">
|
<main className="flex flex-col gap-6">
|
||||||
|
|||||||
@@ -48,21 +48,20 @@ const accountBaseSchema = z.object({
|
|||||||
.string({ message: "Selecione um logo." })
|
.string({ message: "Selecione um logo." })
|
||||||
.trim()
|
.trim()
|
||||||
.min(1, "Selecione um logo."),
|
.min(1, "Selecione um logo."),
|
||||||
initialBalance: z
|
initialBalance: z.union([
|
||||||
.union([
|
z.number(),
|
||||||
z.number(),
|
z
|
||||||
z
|
.string()
|
||||||
.string()
|
.trim()
|
||||||
.trim()
|
.transform((value) =>
|
||||||
.transform((value) =>
|
value.length === 0 ? "0" : value.replace(",", "."),
|
||||||
value.length === 0 ? "0" : value.replace(",", "."),
|
)
|
||||||
)
|
.refine(
|
||||||
.refine(
|
(value) => !Number.isNaN(Number.parseFloat(value)),
|
||||||
(value) => !Number.isNaN(Number.parseFloat(value)),
|
"Informe um saldo inicial válido.",
|
||||||
"Informe um saldo inicial válido.",
|
)
|
||||||
)
|
.transform((value) => Number.parseFloat(value)),
|
||||||
.transform((value) => Number.parseFloat(value)),
|
]),
|
||||||
]),
|
|
||||||
excludeFromBalance: z
|
excludeFromBalance: z
|
||||||
.union([z.boolean(), z.string()])
|
.union([z.boolean(), z.string()])
|
||||||
.transform((value) => value === true || value === "true"),
|
.transform((value) => value === true || value === "true"),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { normalizeDescriptionKey } from "@/features/transactions/lib/import-util
|
|||||||
import { getUserId } from "@/shared/lib/auth/server";
|
import { getUserId } from "@/shared/lib/auth/server";
|
||||||
import { db } from "@/shared/lib/db";
|
import { db } from "@/shared/lib/db";
|
||||||
|
|
||||||
|
|
||||||
// Retorna um map de descriptionKey → categoryId para as descrições fornecidas
|
// Retorna um map de descriptionKey → categoryId para as descrições fornecidas
|
||||||
export async function fetchCategoryMappings(
|
export async function fetchCategoryMappings(
|
||||||
descriptions: string[],
|
descriptions: string[],
|
||||||
@@ -53,7 +52,10 @@ export async function saveCategoryMappings(
|
|||||||
.insert(importCategoryMappings)
|
.insert(importCategoryMappings)
|
||||||
.values(toUpsert)
|
.values(toUpsert)
|
||||||
.onConflictDoUpdate({
|
.onConflictDoUpdate({
|
||||||
target: [importCategoryMappings.userId, importCategoryMappings.descriptionKey],
|
target: [
|
||||||
|
importCategoryMappings.userId,
|
||||||
|
importCategoryMappings.descriptionKey,
|
||||||
|
],
|
||||||
set: {
|
set: {
|
||||||
categoryId: sql`excluded.category_id`,
|
categoryId: sql`excluded.category_id`,
|
||||||
updatedAt: sql`excluded.updated_at`,
|
updatedAt: sql`excluded.updated_at`,
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ const importSchema = z.object({
|
|||||||
accountId: uuidSchema("FinancialAccount").nullable().optional(),
|
accountId: uuidSchema("FinancialAccount").nullable().optional(),
|
||||||
cardId: uuidSchema("Cartão").nullable().optional(),
|
cardId: uuidSchema("Cartão").nullable().optional(),
|
||||||
paymentMethod: z.string().min(1),
|
paymentMethod: z.string().min(1),
|
||||||
invoicePeriod: z.string().regex(/^\d{4}-\d{2}$/, "Período inválido.").nullable().optional(),
|
invoicePeriod: z
|
||||||
|
.string()
|
||||||
|
.regex(/^\d{4}-\d{2}$/, "Período inválido.")
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ImportRow = z.infer<typeof importRowSchema>;
|
export type ImportRow = z.infer<typeof importRowSchema>;
|
||||||
@@ -51,10 +55,7 @@ export async function checkDuplicateFitIds(
|
|||||||
.select({ ofxFitId: transactions.ofxFitId })
|
.select({ ofxFitId: transactions.ofxFitId })
|
||||||
.from(transactions)
|
.from(transactions)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(eq(transactions.userId, userId), inArray(transactions.ofxFitId, ids)),
|
||||||
eq(transactions.userId, userId),
|
|
||||||
inArray(transactions.ofxFitId, ids),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return rows.map((r) => r.ofxFitId).filter((id): id is string => id !== null);
|
return rows.map((r) => r.ofxFitId).filter((id): id is string => id !== null);
|
||||||
@@ -67,10 +68,14 @@ export async function importTransactionsAction(
|
|||||||
const parsed = importSchema.safeParse(input);
|
const parsed = importSchema.safeParse(input);
|
||||||
|
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
return { success: false, error: parsed.error.issues[0]?.message ?? "Dados inválidos." };
|
return {
|
||||||
|
success: false,
|
||||||
|
error: parsed.error.issues[0]?.message ?? "Dados inválidos.",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rows, payerId, accountId, cardId, paymentMethod, invoicePeriod } = parsed.data;
|
const { rows, payerId, accountId, cardId, paymentMethod, invoicePeriod } =
|
||||||
|
parsed.data;
|
||||||
|
|
||||||
// Valida ownership
|
// Valida ownership
|
||||||
const [payerOk, accountOk, cardOk] = await Promise.all([
|
const [payerOk, accountOk, cardOk] = await Promise.all([
|
||||||
@@ -94,14 +99,19 @@ export async function importTransactionsAction(
|
|||||||
|
|
||||||
const records = rows.map((row) => {
|
const records = rows.map((row) => {
|
||||||
const purchaseDate = parseLocalDateString(row.date);
|
const purchaseDate = parseLocalDateString(row.date);
|
||||||
const period = invoicePeriod ?? `${purchaseDate.getFullYear()}-${String(purchaseDate.getMonth() + 1).padStart(2, "0")}`;
|
const period =
|
||||||
|
invoicePeriod ??
|
||||||
|
`${purchaseDate.getFullYear()}-${String(purchaseDate.getMonth() + 1).padStart(2, "0")}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: row.description,
|
name: row.description,
|
||||||
transactionType: row.transactionType === "income" ? "Receita" : "Despesa",
|
transactionType: row.transactionType === "income" ? "Receita" : "Despesa",
|
||||||
condition: "À vista" as const,
|
condition: "À vista" as const,
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
amount: (row.transactionType === "expense" ? -row.amount : row.amount).toFixed(2),
|
amount: (row.transactionType === "expense"
|
||||||
|
? -row.amount
|
||||||
|
: row.amount
|
||||||
|
).toFixed(2),
|
||||||
purchaseDate,
|
purchaseDate,
|
||||||
period,
|
period,
|
||||||
isSettled,
|
isSettled,
|
||||||
@@ -143,10 +153,7 @@ export async function deleteTransactionByFitId(
|
|||||||
await db
|
await db
|
||||||
.delete(transactions)
|
.delete(transactions)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(eq(transactions.userId, userId), eq(transactions.ofxFitId, fitId)),
|
||||||
eq(transactions.userId, userId),
|
|
||||||
eq(transactions.ofxFitId, fitId),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await revalidateForEntity("transactions", userId);
|
await revalidateForEntity("transactions", userId);
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ export function decodeAccountCard(value: string): {
|
|||||||
id: string;
|
id: string;
|
||||||
} | null {
|
} | null {
|
||||||
if (value.startsWith("card:")) return { type: "card", id: value.slice(5) };
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +66,9 @@ export function GlobalFields({
|
|||||||
onBulkCategoryChange,
|
onBulkCategoryChange,
|
||||||
}: GlobalFieldsProps) {
|
}: GlobalFieldsProps) {
|
||||||
const isCard = accountCardValue?.startsWith("card:") ?? false;
|
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");
|
const incomeCategories = categoryOptions.filter((o) => o.group === "receita");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -131,7 +134,10 @@ export function GlobalFields({
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{payerOptions.map((opt) => (
|
{payerOptions.map((opt) => (
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
<SelectItem key={opt.value} value={opt.value}>
|
||||||
<PayerSelectContent label={opt.label} avatarUrl={opt.avatarUrl} />
|
<PayerSelectContent
|
||||||
|
label={opt.label}
|
||||||
|
avatarUrl={opt.avatarUrl}
|
||||||
|
/>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -150,7 +156,10 @@ export function GlobalFields({
|
|||||||
<SelectLabel>Despesa</SelectLabel>
|
<SelectLabel>Despesa</SelectLabel>
|
||||||
{expenseCategories.map((opt) => (
|
{expenseCategories.map((opt) => (
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
<SelectItem key={opt.value} value={opt.value}>
|
||||||
<CategorySelectContent label={opt.label} icon={opt.icon} />
|
<CategorySelectContent
|
||||||
|
label={opt.label}
|
||||||
|
icon={opt.icon}
|
||||||
|
/>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
@@ -163,7 +172,10 @@ export function GlobalFields({
|
|||||||
<SelectLabel>Receita</SelectLabel>
|
<SelectLabel>Receita</SelectLabel>
|
||||||
{incomeCategories.map((opt) => (
|
{incomeCategories.map((opt) => (
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
<SelectItem key={opt.value} value={opt.value}>
|
||||||
<CategorySelectContent label={opt.label} icon={opt.icon} />
|
<CategorySelectContent
|
||||||
|
label={opt.label}
|
||||||
|
icon={opt.icon}
|
||||||
|
/>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
@@ -172,17 +184,17 @@ export function GlobalFields({
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isCard && (
|
{isCard && (
|
||||||
<div className="flex min-w-44 flex-col gap-1.5">
|
<div className="flex min-w-44 flex-col gap-1.5">
|
||||||
<Label>Fatura</Label>
|
<Label>Fatura</Label>
|
||||||
<PeriodPicker
|
<PeriodPicker
|
||||||
value={invoicePeriod ?? ""}
|
value={invoicePeriod ?? ""}
|
||||||
onChange={(v) => onInvoicePeriodChange(v || null)}
|
onChange={(v) => onInvoicePeriodChange(v || null)}
|
||||||
placeholder="Selecionar fatura…"
|
placeholder="Selecionar fatura…"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
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 { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
fetchCategoryMappings,
|
fetchCategoryMappings,
|
||||||
saveCategoryMappings,
|
saveCategoryMappings,
|
||||||
} from "@/features/transactions/actions/category-memory-action";
|
} from "@/features/transactions/actions/category-memory-action";
|
||||||
import { normalizeDescriptionKey } from "@/features/transactions/lib/import-utils";
|
|
||||||
import {
|
import {
|
||||||
checkDuplicateFitIds,
|
checkDuplicateFitIds,
|
||||||
deleteTransactionByFitId,
|
deleteTransactionByFitId,
|
||||||
@@ -27,6 +32,7 @@ import {
|
|||||||
} from "@/features/transactions/components/import/review-table";
|
} from "@/features/transactions/components/import/review-table";
|
||||||
import { UploadZone } from "@/features/transactions/components/import/upload-zone";
|
import { UploadZone } from "@/features/transactions/components/import/upload-zone";
|
||||||
import type { SelectOption } from "@/features/transactions/components/types";
|
import type { SelectOption } from "@/features/transactions/components/types";
|
||||||
|
import { normalizeDescriptionKey } from "@/features/transactions/lib/import-utils";
|
||||||
import { Button } from "@/shared/components/ui/button";
|
import { Button } from "@/shared/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -82,7 +88,8 @@ export function ImportPage({
|
|||||||
...t,
|
...t,
|
||||||
isDuplicate: t.externalId ? duplicates.has(t.externalId) : false,
|
isDuplicate: t.externalId ? duplicates.has(t.externalId) : false,
|
||||||
selected: t.externalId ? !duplicates.has(t.externalId) : true,
|
selected: t.externalId ? !duplicates.has(t.externalId) : true,
|
||||||
categoryId: categoryMappings[normalizeDescriptionKey(t.description)] ?? null,
|
categoryId:
|
||||||
|
categoryMappings[normalizeDescriptionKey(t.description)] ?? null,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -167,7 +174,9 @@ export function ImportPage({
|
|||||||
const handleImport = () => {
|
const handleImport = () => {
|
||||||
if (!statement || !canImport) return;
|
if (!statement || !canImport) return;
|
||||||
|
|
||||||
const decoded = decodeAccountCard(accountCardValue!);
|
const decoded = accountCardValue
|
||||||
|
? decodeAccountCard(accountCardValue)
|
||||||
|
: null;
|
||||||
const cardId = decoded?.type === "card" ? decoded.id : null;
|
const cardId = decoded?.type === "card" ? decoded.id : null;
|
||||||
const accountId = decoded?.type === "account" ? decoded.id : null;
|
const accountId = decoded?.type === "account" ? decoded.id : null;
|
||||||
const paymentMethod =
|
const paymentMethod =
|
||||||
@@ -197,7 +206,10 @@ export function ImportPage({
|
|||||||
|
|
||||||
// Salva mapeamentos description → category (fire-and-forget)
|
// Salva mapeamentos description → category (fire-and-forget)
|
||||||
saveCategoryMappings(
|
saveCategoryMappings(
|
||||||
selectedRows.map((r) => ({ description: r.description, categoryId: r.categoryId })),
|
selectedRows.map((r) => ({
|
||||||
|
description: r.description,
|
||||||
|
categoryId: r.categoryId,
|
||||||
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { importBatchId } = result;
|
const { importBatchId } = result;
|
||||||
@@ -236,7 +248,8 @@ export function ImportPage({
|
|||||||
<div>
|
<div>
|
||||||
<CardTitle>Importar extrato</CardTitle>
|
<CardTitle>Importar extrato</CardTitle>
|
||||||
<CardDescription>
|
<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>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<ImportSteps current={currentStep} />
|
<ImportSteps current={currentStep} />
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ export function ImportSteps({ current }: ImportStepsProps) {
|
|||||||
isCompleted &&
|
isCompleted &&
|
||||||
"border-primary bg-primary text-primary-foreground",
|
"border-primary bg-primary text-primary-foreground",
|
||||||
isActive && "border-primary text-primary",
|
isActive && "border-primary text-primary",
|
||||||
!isCompleted && !isActive && "border-muted-foreground/30 text-muted-foreground",
|
!isCompleted &&
|
||||||
|
!isActive &&
|
||||||
|
"border-muted-foreground/30 text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isCompleted ? (
|
{isCompleted ? (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRef } from "react";
|
|
||||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||||
|
import { useRef } from "react";
|
||||||
import { CategorySelectContent } from "@/features/transactions/components/select-items";
|
import { CategorySelectContent } from "@/features/transactions/components/select-items";
|
||||||
import type { SelectOption } from "@/features/transactions/components/types";
|
import type { SelectOption } from "@/features/transactions/components/types";
|
||||||
import MoneyValues from "@/shared/components/money-values";
|
import MoneyValues from "@/shared/components/money-values";
|
||||||
@@ -91,9 +91,7 @@ export function ReviewTable({
|
|||||||
onCheckedChange={(v) => onToggleAll(!!v)}
|
onCheckedChange={(v) => onToggleAll(!!v)}
|
||||||
aria-label="Selecionar todas"
|
aria-label="Selecionar todas"
|
||||||
data-state={
|
data-state={
|
||||||
!allSelected && someSelected
|
!allSelected && someSelected ? "indeterminate" : undefined
|
||||||
? "indeterminate"
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
@@ -114,7 +112,10 @@ export function ReviewTable({
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
{virtualRows.map((virtualRow) => {
|
{virtualRows.map((virtualRow) => {
|
||||||
const row = rows[virtualRow.index]!;
|
const row = rows[virtualRow.index];
|
||||||
|
if (!row) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const index = virtualRow.index;
|
const index = virtualRow.index;
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
@@ -199,9 +200,7 @@ export function ReviewTable({
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<TransactionTypeBadge
|
<TransactionTypeBadge
|
||||||
kind={
|
kind={
|
||||||
row.transactionType === "income"
|
row.transactionType === "income" ? "Receita" : "Despesa"
|
||||||
? "Receita"
|
|
||||||
: "Despesa"
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ export function UploadZone({ onParsed }: UploadZoneProps) {
|
|||||||
}
|
}
|
||||||
onParsed(statement);
|
onParsed(statement);
|
||||||
} catch {
|
} 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");
|
reader.readAsText(file, "windows-1252");
|
||||||
@@ -119,11 +121,7 @@ export function UploadZone({ onParsed }: UploadZoneProps) {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
{error ? (
|
{error ? <p className="text-destructive text-sm">{error}</p> : <span />}
|
||||||
<p className="text-destructive text-sm">{error}</p>
|
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleDownloadTemplate}
|
onClick={handleDownloadTemplate}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import {
|
|||||||
RiCheckLine,
|
RiCheckLine,
|
||||||
RiDeleteBin5Line,
|
RiDeleteBin5Line,
|
||||||
RiFileCopyLine,
|
RiFileCopyLine,
|
||||||
|
RiFileExcel2Line,
|
||||||
RiFileList2Line,
|
RiFileList2Line,
|
||||||
RiFlashlightFill,
|
RiFlashlightFill,
|
||||||
RiFileExcel2Line,
|
|
||||||
RiGroupLine,
|
RiGroupLine,
|
||||||
RiHistoryLine,
|
RiHistoryLine,
|
||||||
RiMoreFill,
|
RiMoreFill,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ImportStatement, ImportedTransaction } from "./types";
|
import type { ImportedTransaction, ImportStatement } from "./types";
|
||||||
|
|
||||||
// Extrai o valor de uma tag leaf do OFX SGML: <TAG>valor
|
// Extrai o valor de uma tag leaf do OFX SGML: <TAG>valor
|
||||||
function getField(block: string, tag: string): string | null {
|
function getField(block: string, tag: string): string | null {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import * as XLSX from "xlsx";
|
import * as XLSX from "xlsx";
|
||||||
import type { ImportStatement, ImportedTransaction } from "@/shared/lib/import/types";
|
import type {
|
||||||
|
ImportedTransaction,
|
||||||
|
ImportStatement,
|
||||||
|
} from "@/shared/lib/import/types";
|
||||||
|
|
||||||
function parseDateValue(value: unknown): string | null {
|
function parseDateValue(value: unknown): string | null {
|
||||||
if (value == null || value === "") return null;
|
if (value == null || value === "") return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user