refactor: standardize quotation marks across all files and improve code consistency

This commit is contained in:
Julien Froidefond
2025-11-27 11:40:30 +01:00
parent cc1e8c20a6
commit b2efade4d5
107 changed files with 9471 additions and 5952 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +1,68 @@
"use client"
"use client";
import { useState, useEffect, useCallback } from "react"
import type { BankingData } from "./types"
import { loadData } from "./store-db"
import { useState, useEffect, useCallback } from "react";
import type { BankingData } from "./types";
import { loadData } from "./store-db";
export function useBankingData() {
const [data, setData] = useState<BankingData | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
const [data, setData] = useState<BankingData | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = useCallback(async () => {
try {
setIsLoading(true)
setError(null)
const fetchedData = await loadData()
setData(fetchedData)
setIsLoading(true);
setError(null);
const fetchedData = await loadData();
setData(fetchedData);
} catch (err) {
setError(err instanceof Error ? err : new Error("Failed to load data"))
console.error("Error loading banking data:", err)
setError(err instanceof Error ? err : new Error("Failed to load data"));
console.error("Error loading banking data:", err);
} finally {
setIsLoading(false)
setIsLoading(false);
}
}, [])
}, []);
useEffect(() => {
fetchData()
}, [fetchData])
fetchData();
}, [fetchData]);
const refresh = useCallback(() => {
fetchData()
}, [fetchData])
fetchData();
}, [fetchData]);
const update = useCallback((newData: BankingData) => {
// Optimistic update - the actual save happens in individual operations
setData(newData)
}, [])
setData(newData);
}, []);
return { data, isLoading, error, refresh, update }
return { data, isLoading, error, refresh, update };
}
export function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(initialValue)
const [storedValue, setStoredValue] = useState<T>(initialValue);
useEffect(() => {
try {
const item = window.localStorage.getItem(key)
const item = window.localStorage.getItem(key);
if (item) {
setStoredValue(JSON.parse(item))
setStoredValue(JSON.parse(item));
}
} catch (error) {
console.error(error)
console.error(error);
}
}, [key])
}, [key]);
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error)
console.error(error);
}
}
};
return [storedValue, setValue] as const
return [storedValue, setValue] as const;
}

View File

@@ -1,40 +1,45 @@
import type { OFXAccount, OFXTransaction } from "./types"
import type { OFXAccount, OFXTransaction } from "./types";
export function parseOFX(content: string): OFXAccount | null {
try {
// Remove SGML header and clean up
const xmlStart = content.indexOf("<OFX>")
if (xmlStart === -1) return null
const xmlStart = content.indexOf("<OFX>");
if (xmlStart === -1) return null;
let xml = content.substring(xmlStart)
let xml = content.substring(xmlStart);
// Convert SGML to XML-like format
xml = xml.replace(/<(\w+)>([^<]+)(?=<)/g, "<$1>$2</$1>")
xml = xml.replace(/<(\w+)>([^<]+)(?=<)/g, "<$1>$2</$1>");
// Extract account info
const bankId = extractValue(xml, "BANKID") || extractValue(xml, "ORG") || "UNKNOWN"
const accountId = extractValue(xml, "ACCTID") || "UNKNOWN"
const accountType = extractValue(xml, "ACCTTYPE") || "CHECKING"
const balanceStr = extractValue(xml, "BALAMT") || "0"
const balance = Number.parseFloat(balanceStr)
const balanceDate = extractValue(xml, "DTASOF") || new Date().toISOString()
const currency = extractValue(xml, "CURDEF") || "EUR"
const bankId =
extractValue(xml, "BANKID") || extractValue(xml, "ORG") || "UNKNOWN";
const accountId = extractValue(xml, "ACCTID") || "UNKNOWN";
const accountType = extractValue(xml, "ACCTTYPE") || "CHECKING";
const balanceStr = extractValue(xml, "BALAMT") || "0";
const balance = Number.parseFloat(balanceStr);
const balanceDate = extractValue(xml, "DTASOF") || new Date().toISOString();
const currency = extractValue(xml, "CURDEF") || "EUR";
// Extract transactions
const transactions: OFXTransaction[] = []
const stmtTrnRegex = /<STMTTRN>([\s\S]*?)<\/STMTTRN>/gi
let match
const transactions: OFXTransaction[] = [];
const stmtTrnRegex = /<STMTTRN>([\s\S]*?)<\/STMTTRN>/gi;
let match;
while ((match = stmtTrnRegex.exec(xml)) !== null) {
const trnXml = match[1]
const trnXml = match[1];
const fitId = extractValue(trnXml, "FITID") || `${Date.now()}-${Math.random()}`
const dateStr = extractValue(trnXml, "DTPOSTED") || ""
const amountStr = extractValue(trnXml, "TRNAMT") || "0"
const name = extractValue(trnXml, "NAME") || extractValue(trnXml, "MEMO") || "Unknown"
const memo = extractValue(trnXml, "MEMO")
const checkNum = extractValue(trnXml, "CHECKNUM")
const type = extractValue(trnXml, "TRNTYPE") || "OTHER"
const fitId =
extractValue(trnXml, "FITID") || `${Date.now()}-${Math.random()}`;
const dateStr = extractValue(trnXml, "DTPOSTED") || "";
const amountStr = extractValue(trnXml, "TRNAMT") || "0";
const name =
extractValue(trnXml, "NAME") ||
extractValue(trnXml, "MEMO") ||
"Unknown";
const memo = extractValue(trnXml, "MEMO");
const checkNum = extractValue(trnXml, "CHECKNUM");
const type = extractValue(trnXml, "TRNTYPE") || "OTHER";
transactions.push({
fitId,
@@ -44,7 +49,7 @@ export function parseOFX(content: string): OFXAccount | null {
memo: memo ? cleanString(memo) : undefined,
checkNum: checkNum ?? undefined,
type,
})
});
}
return {
@@ -55,37 +60,39 @@ export function parseOFX(content: string): OFXAccount | null {
balanceDate: parseOFXDate(balanceDate),
currency,
transactions,
}
};
} catch (error) {
console.error("Error parsing OFX:", error)
return null
console.error("Error parsing OFX:", error);
return null;
}
}
function extractValue(xml: string, tag: string): string | null {
const regex = new RegExp(`<${tag}>([^<]+)`, "i")
const match = xml.match(regex)
return match ? match[1].trim() : null
const regex = new RegExp(`<${tag}>([^<]+)`, "i");
const match = xml.match(regex);
return match ? match[1].trim() : null;
}
function parseOFXDate(dateStr: string): string {
if (!dateStr || dateStr.length < 8) return new Date().toISOString()
if (!dateStr || dateStr.length < 8) return new Date().toISOString();
const year = dateStr.substring(0, 4)
const month = dateStr.substring(4, 6)
const day = dateStr.substring(6, 8)
const year = dateStr.substring(0, 4);
const month = dateStr.substring(4, 6);
const day = dateStr.substring(6, 8);
return `${year}-${month}-${day}`
return `${year}-${month}-${day}`;
}
function mapAccountType(type: string): "CHECKING" | "SAVINGS" | "CREDIT_CARD" | "OTHER" {
const upper = type.toUpperCase()
if (upper.includes("CHECK") || upper.includes("CURRENT")) return "CHECKING"
if (upper.includes("SAV")) return "SAVINGS"
if (upper.includes("CREDIT")) return "CREDIT_CARD"
return "OTHER"
function mapAccountType(
type: string,
): "CHECKING" | "SAVINGS" | "CREDIT_CARD" | "OTHER" {
const upper = type.toUpperCase();
if (upper.includes("CHECK") || upper.includes("CURRENT")) return "CHECKING";
if (upper.includes("SAV")) return "SAVINGS";
if (upper.includes("CREDIT")) return "CREDIT_CARD";
return "OTHER";
}
function cleanString(str: string): string {
return str.replace(/\s+/g, " ").trim()
return str.replace(/\s+/g, " ").trim();
}

View File

@@ -1,14 +1,16 @@
import { PrismaClient } from "@prisma/client"
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
})
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma
log:
process.env.NODE_ENV === "development"
? ["query", "error", "warn"]
: ["error"],
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;

View File

@@ -1,27 +1,35 @@
"use client"
"use client";
import type { BankingData, Account, Transaction, Folder, Category } from "./types"
import type {
BankingData,
Account,
Transaction,
Folder,
Category,
} from "./types";
const API_BASE = "/api/banking"
const API_BASE = "/api/banking";
export async function loadData(): Promise<BankingData> {
const response = await fetch(API_BASE)
const response = await fetch(API_BASE);
if (!response.ok) {
throw new Error("Failed to load data")
throw new Error("Failed to load data");
}
return response.json()
return response.json();
}
export async function addAccount(account: Omit<Account, "id">): Promise<Account> {
export async function addAccount(
account: Omit<Account, "id">,
): Promise<Account> {
const response = await fetch(`${API_BASE}/accounts`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(account),
})
});
if (!response.ok) {
throw new Error("Failed to add account")
throw new Error("Failed to add account");
}
return response.json()
return response.json();
}
export async function updateAccount(account: Account): Promise<Account> {
@@ -29,44 +37,48 @@ export async function updateAccount(account: Account): Promise<Account> {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(account),
})
});
if (!response.ok) {
throw new Error("Failed to update account")
throw new Error("Failed to update account");
}
return response.json()
return response.json();
}
export async function deleteAccount(accountId: string): Promise<void> {
const response = await fetch(`${API_BASE}/accounts?id=${accountId}`, {
method: "DELETE",
})
});
if (!response.ok) {
throw new Error("Failed to delete account")
throw new Error("Failed to delete account");
}
}
export async function addTransactions(transactions: Transaction[]): Promise<{ count: number; transactions: Transaction[] }> {
export async function addTransactions(
transactions: Transaction[],
): Promise<{ count: number; transactions: Transaction[] }> {
const response = await fetch(`${API_BASE}/transactions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(transactions),
})
});
if (!response.ok) {
throw new Error("Failed to add transactions")
throw new Error("Failed to add transactions");
}
return response.json()
return response.json();
}
export async function updateTransaction(transaction: Transaction): Promise<Transaction> {
export async function updateTransaction(
transaction: Transaction,
): Promise<Transaction> {
const response = await fetch(`${API_BASE}/transactions`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(transaction),
})
});
if (!response.ok) {
throw new Error("Failed to update transaction")
throw new Error("Failed to update transaction");
}
return response.json()
return response.json();
}
export async function addFolder(folder: Omit<Folder, "id">): Promise<Folder> {
@@ -74,11 +86,11 @@ export async function addFolder(folder: Omit<Folder, "id">): Promise<Folder> {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(folder),
})
});
if (!response.ok) {
throw new Error("Failed to add folder")
throw new Error("Failed to add folder");
}
return response.json()
return response.json();
}
export async function updateFolder(folder: Folder): Promise<Folder> {
@@ -86,32 +98,34 @@ export async function updateFolder(folder: Folder): Promise<Folder> {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(folder),
})
});
if (!response.ok) {
throw new Error("Failed to update folder")
throw new Error("Failed to update folder");
}
return response.json()
return response.json();
}
export async function deleteFolder(folderId: string): Promise<void> {
const response = await fetch(`${API_BASE}/folders?id=${folderId}`, {
method: "DELETE",
})
});
if (!response.ok) {
throw new Error("Failed to delete folder")
throw new Error("Failed to delete folder");
}
}
export async function addCategory(category: Omit<Category, "id">): Promise<Category> {
export async function addCategory(
category: Omit<Category, "id">,
): Promise<Category> {
const response = await fetch(`${API_BASE}/categories`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(category),
})
});
if (!response.ok) {
throw new Error("Failed to add category")
throw new Error("Failed to add category");
}
return response.json()
return response.json();
}
export async function updateCategory(category: Category): Promise<Category> {
@@ -119,55 +133,57 @@ export async function updateCategory(category: Category): Promise<Category> {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(category),
})
});
if (!response.ok) {
throw new Error("Failed to update category")
throw new Error("Failed to update category");
}
return response.json()
return response.json();
}
export async function deleteCategory(categoryId: string): Promise<void> {
const response = await fetch(`${API_BASE}/categories?id=${categoryId}`, {
method: "DELETE",
})
});
if (!response.ok) {
throw new Error("Failed to delete category")
throw new Error("Failed to delete category");
}
}
// Auto-categorize a transaction based on keywords
export function autoCategorize(description: string, categories: Category[]): string | null {
const lowerDesc = description.toLowerCase()
export function autoCategorize(
description: string,
categories: Category[],
): string | null {
const lowerDesc = description.toLowerCase();
for (const category of categories) {
for (const keyword of category.keywords) {
const lowerKeyword = keyword.toLowerCase()
const lowerKeyword = keyword.toLowerCase();
// Pour les keywords courts (< 6 chars), matcher uniquement des mots entiers
// Évite les faux positifs comme "chat" dans "achat"
if (lowerKeyword.length < 6) {
const wordBoundary = new RegExp(`\\b${escapeRegex(lowerKeyword)}\\b`)
const wordBoundary = new RegExp(`\\b${escapeRegex(lowerKeyword)}\\b`);
if (wordBoundary.test(lowerDesc)) {
return category.id
return category.id;
}
} else {
// Pour les keywords plus longs, includes() suffit
if (lowerDesc.includes(lowerKeyword)) {
return category.id
return category.id;
}
}
}
}
return null
return null;
}
// Échappe les caractères spéciaux pour les regex
function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
export function generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

View File

@@ -1,20 +1,26 @@
"use client"
"use client";
import type { BankingData, Account, Transaction, Folder, Category } from "./types"
import { defaultCategories, defaultRootFolder } from "./defaults"
import type {
BankingData,
Account,
Transaction,
Folder,
Category,
} from "./types";
import { defaultCategories, defaultRootFolder } from "./defaults";
const STORAGE_KEY = "banking-app-data"
const STORAGE_KEY = "banking-app-data";
// Convertir les CategoryDefinition en Category pour le localStorage
function buildCategoriesFromDefaults(): Category[] {
const slugToId = new Map<string, string>()
const categories: Category[] = []
const slugToId = new Map<string, string>();
const categories: Category[] = [];
// D'abord les parents
const parents = defaultCategories.filter((c) => c.parentSlug === null)
const parents = defaultCategories.filter((c) => c.parentSlug === null);
parents.forEach((cat, index) => {
const id = `cat-${index + 1}`
slugToId.set(cat.slug, id)
const id = `cat-${index + 1}`;
slugToId.set(cat.slug, id);
categories.push({
id,
name: cat.name,
@@ -22,14 +28,14 @@ function buildCategoriesFromDefaults(): Category[] {
icon: cat.icon,
keywords: cat.keywords,
parentId: null,
})
})
});
});
// Puis les enfants
const children = defaultCategories.filter((c) => c.parentSlug !== null)
const children = defaultCategories.filter((c) => c.parentSlug !== null);
children.forEach((cat, index) => {
const id = `cat-${parents.length + index + 1}`
slugToId.set(cat.slug, id)
const id = `cat-${parents.length + index + 1}`;
slugToId.set(cat.slug, id);
categories.push({
id,
name: cat.name,
@@ -37,10 +43,10 @@ function buildCategoriesFromDefaults(): Category[] {
icon: cat.icon,
keywords: cat.keywords,
parentId: cat.parentSlug ? slugToId.get(cat.parentSlug) || null : null,
})
})
});
});
return categories
return categories;
}
const defaultData: BankingData = {
@@ -48,148 +54,163 @@ const defaultData: BankingData = {
transactions: [],
folders: [defaultRootFolder],
categories: buildCategoriesFromDefaults(),
}
};
export function loadData(): BankingData {
if (typeof window === "undefined") return defaultData
if (typeof window === "undefined") return defaultData;
const stored = localStorage.getItem(STORAGE_KEY)
const stored = localStorage.getItem(STORAGE_KEY);
if (!stored) {
saveData(defaultData)
return defaultData
saveData(defaultData);
return defaultData;
}
try {
return JSON.parse(stored)
return JSON.parse(stored);
} catch {
return defaultData
return defaultData;
}
}
export function saveData(data: BankingData): void {
if (typeof window === "undefined") return
localStorage.setItem(STORAGE_KEY, JSON.stringify(data))
if (typeof window === "undefined") return;
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
}
export function addAccount(account: Account): BankingData {
const data = loadData()
data.accounts.push(account)
saveData(data)
return data
const data = loadData();
data.accounts.push(account);
saveData(data);
return data;
}
export function updateAccount(account: Account): BankingData {
const data = loadData()
const index = data.accounts.findIndex((a) => a.id === account.id)
const data = loadData();
const index = data.accounts.findIndex((a) => a.id === account.id);
if (index !== -1) {
data.accounts[index] = account
saveData(data)
data.accounts[index] = account;
saveData(data);
}
return data
return data;
}
export function deleteAccount(accountId: string): BankingData {
const data = loadData()
data.accounts = data.accounts.filter((a) => a.id !== accountId)
data.transactions = data.transactions.filter((t) => t.accountId !== accountId)
saveData(data)
return data
const data = loadData();
data.accounts = data.accounts.filter((a) => a.id !== accountId);
data.transactions = data.transactions.filter(
(t) => t.accountId !== accountId,
);
saveData(data);
return data;
}
export function addTransactions(transactions: Transaction[]): BankingData {
const data = loadData()
const data = loadData();
// Filter out duplicates based on fitId
const existingFitIds = new Set(data.transactions.map((t) => `${t.accountId}-${t.fitId}`))
const newTransactions = transactions.filter((t) => !existingFitIds.has(`${t.accountId}-${t.fitId}`))
const existingFitIds = new Set(
data.transactions.map((t) => `${t.accountId}-${t.fitId}`),
);
const newTransactions = transactions.filter(
(t) => !existingFitIds.has(`${t.accountId}-${t.fitId}`),
);
data.transactions.push(...newTransactions)
saveData(data)
return data
data.transactions.push(...newTransactions);
saveData(data);
return data;
}
export function updateTransaction(transaction: Transaction): BankingData {
const data = loadData()
const index = data.transactions.findIndex((t) => t.id === transaction.id)
const data = loadData();
const index = data.transactions.findIndex((t) => t.id === transaction.id);
if (index !== -1) {
data.transactions[index] = transaction
saveData(data)
data.transactions[index] = transaction;
saveData(data);
}
return data
return data;
}
export function addFolder(folder: Folder): BankingData {
const data = loadData()
data.folders.push(folder)
saveData(data)
return data
const data = loadData();
data.folders.push(folder);
saveData(data);
return data;
}
export function updateFolder(folder: Folder): BankingData {
const data = loadData()
const index = data.folders.findIndex((f) => f.id === folder.id)
const data = loadData();
const index = data.folders.findIndex((f) => f.id === folder.id);
if (index !== -1) {
data.folders[index] = folder
saveData(data)
data.folders[index] = folder;
saveData(data);
}
return data
return data;
}
export function deleteFolder(folderId: string): BankingData {
const data = loadData()
const data = loadData();
// Move accounts to root
data.accounts = data.accounts.map((a) => (a.folderId === folderId ? { ...a, folderId: "folder-root" } : a))
data.accounts = data.accounts.map((a) =>
a.folderId === folderId ? { ...a, folderId: "folder-root" } : a,
);
// Move subfolders to parent
const folder = data.folders.find((f) => f.id === folderId)
const folder = data.folders.find((f) => f.id === folderId);
if (folder) {
data.folders = data.folders.map((f) => (f.parentId === folderId ? { ...f, parentId: folder.parentId } : f))
data.folders = data.folders.map((f) =>
f.parentId === folderId ? { ...f, parentId: folder.parentId } : f,
);
}
data.folders = data.folders.filter((f) => f.id !== folderId)
saveData(data)
return data
data.folders = data.folders.filter((f) => f.id !== folderId);
saveData(data);
return data;
}
export function addCategory(category: Category): BankingData {
const data = loadData()
data.categories.push(category)
saveData(data)
return data
const data = loadData();
data.categories.push(category);
saveData(data);
return data;
}
export function updateCategory(category: Category): BankingData {
const data = loadData()
const index = data.categories.findIndex((c) => c.id === category.id)
const data = loadData();
const index = data.categories.findIndex((c) => c.id === category.id);
if (index !== -1) {
data.categories[index] = category
saveData(data)
data.categories[index] = category;
saveData(data);
}
return data
return data;
}
export function deleteCategory(categoryId: string): BankingData {
const data = loadData()
data.categories = data.categories.filter((c) => c.id !== categoryId)
const data = loadData();
data.categories = data.categories.filter((c) => c.id !== categoryId);
// Remove category from transactions
data.transactions = data.transactions.map((t) => (t.categoryId === categoryId ? { ...t, categoryId: null } : t))
saveData(data)
return data
data.transactions = data.transactions.map((t) =>
t.categoryId === categoryId ? { ...t, categoryId: null } : t,
);
saveData(data);
return data;
}
// Auto-categorize a transaction based on keywords
export function autoCategorize(description: string, categories: Category[]): string | null {
const lowerDesc = description.toLowerCase()
export function autoCategorize(
description: string,
categories: Category[],
): string | null {
const lowerDesc = description.toLowerCase();
for (const category of categories) {
for (const keyword of category.keywords) {
if (lowerDesc.includes(keyword.toLowerCase())) {
return category.id
return category.id;
}
}
}
return null
return null;
}
export function generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

View File

@@ -1,72 +1,72 @@
// Types for the banking management application
export interface Transaction {
id: string
accountId: string
date: string
amount: number
description: string
type: "DEBIT" | "CREDIT"
categoryId: string | null
isReconciled: boolean
fitId: string // OFX unique transaction ID
memo?: string
checkNum?: string
id: string;
accountId: string;
date: string;
amount: number;
description: string;
type: "DEBIT" | "CREDIT";
categoryId: string | null;
isReconciled: boolean;
fitId: string; // OFX unique transaction ID
memo?: string;
checkNum?: string;
}
export interface Account {
id: string
name: string
bankId: string
accountNumber: string
type: "CHECKING" | "SAVINGS" | "CREDIT_CARD" | "OTHER"
folderId: string | null
balance: number
currency: string
lastImport: string | null
id: string;
name: string;
bankId: string;
accountNumber: string;
type: "CHECKING" | "SAVINGS" | "CREDIT_CARD" | "OTHER";
folderId: string | null;
balance: number;
currency: string;
lastImport: string | null;
}
export interface Folder {
id: string
name: string
parentId: string | null
color: string
icon: string
id: string;
name: string;
parentId: string | null;
color: string;
icon: string;
}
export interface Category {
id: string
name: string
color: string
icon: string
keywords: string[] // For auto-categorization
parentId: string | null
id: string;
name: string;
color: string;
icon: string;
keywords: string[]; // For auto-categorization
parentId: string | null;
}
export interface BankingData {
accounts: Account[]
transactions: Transaction[]
folders: Folder[]
categories: Category[]
accounts: Account[];
transactions: Transaction[];
folders: Folder[];
categories: Category[];
}
// OFX Parsed types
export interface OFXTransaction {
fitId: string
date: string
amount: number
name: string
memo?: string
checkNum?: string
type: string
fitId: string;
date: string;
amount: number;
name: string;
memo?: string;
checkNum?: string;
type: string;
}
export interface OFXAccount {
bankId: string
accountId: string
accountType: string
balance: number
balanceDate: string
currency: string
transactions: OFXTransaction[]
bankId: string;
accountId: string;
accountType: string;
balance: number;
balanceDate: string;
currency: string;
transactions: OFXTransaction[];
}

View File

@@ -1,6 +1,6 @@
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
return twMerge(clsx(inputs));
}