fix: CSV import bugs and dashboard category filtering
Some checks failed
Release / build (windows-latest) (push) Has been cancelled

- Fix column display for Desjardins-style quoted CSVs (apply preprocessQuotedCSV in header loading)
- Fix column mapping disappearing on back-navigation (generate synthetic headers when hasHeader is false)
- Fix auto-detect picking account number as amount (exclude constant-value columns, treat 0 as empty in debit/credit detection)
- Use category type instead of amount sign for dashboard pie chart and recent transactions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Le-King-Fu 2026-02-12 00:09:10 +00:00
parent 96ce5f3396
commit 474c7b947a
4 changed files with 18 additions and 7 deletions

View file

@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Simpl Résultat", "productName": "Simpl Résultat",
"version": "0.1.1", "version": "0.1.2",
"identifier": "com.simpl.resultat", "identifier": "com.simpl.resultat",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",

View file

@ -338,7 +338,8 @@ export function useImportWizard() {
encoding, encoding,
maxLines: skipLines + 5, maxLines: skipLines + 5,
}); });
const parsed = Papa.parse(preview, { delimiter, skipEmptyLines: true }); const preprocessed = preprocessQuotedCSV(preview);
const parsed = Papa.parse(preprocessed, { delimiter, skipEmptyLines: true });
const data = parsed.data as string[][]; const data = parsed.data as string[][];
const headerRow = hasHeader && data.length > skipLines ? skipLines : -1; const headerRow = hasHeader && data.length > skipLines ? skipLines : -1;
if (headerRow >= 0 && data[headerRow]) { if (headerRow >= 0 && data[headerRow]) {
@ -445,6 +446,9 @@ export function useImportWizard() {
if (config.hasHeader && data.length > config.skipLines) { if (config.hasHeader && data.length > config.skipLines) {
headers = data[config.skipLines].map((h) => h.trim()); headers = data[config.skipLines].map((h) => h.trim());
} else if (!config.hasHeader && headers.length === 0 && data.length > config.skipLines) {
const firstDataRow = data[config.skipLines];
headers = firstDataRow.map((_, i) => `Col ${i}`);
} }
for (let i = startIdx; i < data.length; i++) { for (let i = startIdx; i < data.length; i++) {

View file

@ -56,7 +56,7 @@ export async function getExpensesByCategory(
): Promise<CategoryBreakdownItem[]> { ): Promise<CategoryBreakdownItem[]> {
const db = await getDb(); const db = await getDb();
const whereClauses: string[] = ["t.amount < 0"]; const whereClauses: string[] = ["COALESCE(c.type, 'expense') = 'expense'"];
const params: unknown[] = []; const params: unknown[] = [];
let paramIndex = 1; let paramIndex = 1;
@ -99,7 +99,7 @@ export async function getRecentTransactions(
c.name AS category_name, c.color AS category_color c.name AS category_name, c.color AS category_color
FROM transactions t FROM transactions t
LEFT JOIN categories c ON t.category_id = c.id LEFT JOIN categories c ON t.category_id = c.id
WHERE t.amount < 0 WHERE COALESCE(c.type, 'expense') = 'expense'
ORDER BY t.date DESC, t.id DESC ORDER BY t.date DESC, t.id DESC
LIMIT $1`, LIMIT $1`,
[limit] [limit]

View file

@ -220,17 +220,22 @@ function detectNumericColumns(rows: string[][], colCount: number): number[] {
for (let col = 0; col < colCount; col++) { for (let col = 0; col < colCount; col++) {
let numericCount = 0; let numericCount = 0;
let nonEmpty = 0; let nonEmpty = 0;
const distinctValues = new Set<number>();
for (const row of rows) { for (const row of rows) {
const cell = row[col]?.trim(); const cell = row[col]?.trim();
if (!cell) continue; if (!cell) continue;
nonEmpty++; nonEmpty++;
if (!isNaN(parseFrenchAmount(cell))) { const val = parseFrenchAmount(cell);
if (!isNaN(val)) {
numericCount++; numericCount++;
distinctValues.add(val);
} }
} }
if (nonEmpty > 0 && numericCount / nonEmpty >= 0.5) { if (nonEmpty > 0 && numericCount / nonEmpty >= 0.5) {
// Exclude constant-value columns (e.g., account numbers, transit numbers)
if (distinctValues.size <= 1 && nonEmpty > 2) continue;
result.push(col); result.push(col);
} }
} }
@ -444,8 +449,10 @@ function isSparseComplementary(
for (const row of rows) { for (const row of rows) {
const cellA = row[colA]?.trim(); const cellA = row[colA]?.trim();
const cellB = row[colB]?.trim(); const cellB = row[colB]?.trim();
const hasA = cellA !== "" && cellA != null && !isNaN(parseFrenchAmount(cellA)); const valA = cellA ? parseFrenchAmount(cellA) : NaN;
const hasB = cellB !== "" && cellB != null && !isNaN(parseFrenchAmount(cellB)); const valB = cellB ? parseFrenchAmount(cellB) : NaN;
const hasA = !isNaN(valA) && valA !== 0;
const hasB = !isNaN(valB) && valB !== 0;
if (!hasA && !hasB) continue; if (!hasA && !hasB) continue;
total++; total++;