fix: CSV import bugs and dashboard category filtering
Some checks failed
Release / build (windows-latest) (push) Has been cancelled
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:
parent
96ce5f3396
commit
474c7b947a
4 changed files with 18 additions and 7 deletions
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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++) {
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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++;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue