From e23e559ee3d76f1e5e3b60fe72d34b70b1818b7e Mon Sep 17 00:00:00 2001 From: Le-King-Fu Date: Sun, 15 Feb 2026 13:38:51 +0000 Subject: [PATCH] feat: make settings data imports visible in Import History Create import_sources + imported_files tracking records when importing transactions from Settings > Data Management, so imports appear in the Import History panel and can be deleted like CSV imports. Co-Authored-By: Claude Opus 4.6 --- src/hooks/useDataImport.ts | 13 ++++--- src/services/dataExportService.ts | 56 ++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/hooks/useDataImport.ts b/src/hooks/useDataImport.ts index d7db07c..4bfc0bc 100644 --- a/src/hooks/useDataImport.ts +++ b/src/hooks/useDataImport.ts @@ -33,6 +33,7 @@ type ImportAction = | { type: "NEEDS_PASSWORD"; filePath: string } | { type: "CONFIRMING"; + filePath: string; summary: ImportSummary; data: ExportEnvelope["data"]; importType: ExportEnvelope["export_type"]; @@ -61,6 +62,7 @@ function reducer(state: ImportState, action: ImportAction): ImportState { return { ...state, status: "confirming", + filePath: action.filePath, summary: action.summary, parsedData: action.data, importType: action.importType, @@ -132,7 +134,7 @@ export function useDataImport() { }); const { summary, data, importType } = parseContent(content, filePath); - dispatch({ type: "CONFIRMING", summary, data, importType }); + dispatch({ type: "CONFIRMING", filePath, summary, data, importType }); } catch (e) { dispatch({ type: "IMPORT_ERROR", @@ -152,7 +154,7 @@ export function useDataImport() { }); const { summary, data, importType } = parseContent(content, state.filePath); - dispatch({ type: "CONFIRMING", summary, data, importType }); + dispatch({ type: "CONFIRMING", filePath: state.filePath, summary, data, importType }); } catch (e) { dispatch({ type: "IMPORT_ERROR", @@ -166,16 +168,17 @@ export function useDataImport() { const executeImport = useCallback(async () => { if (!state.parsedData || !state.importType) return; dispatch({ type: "IMPORT_START" }); + const filename = state.filePath?.split(/[/\\]/).pop() ?? "unknown"; try { switch (state.importType) { case "categories_only": await importCategoriesOnly(state.parsedData); break; case "transactions_with_categories": - await importTransactionsWithCategories(state.parsedData); + await importTransactionsWithCategories(state.parsedData, filename); break; case "transactions_only": - await importTransactionsOnly(state.parsedData); + await importTransactionsOnly(state.parsedData, filename); break; } dispatch({ type: "IMPORT_SUCCESS" }); @@ -185,7 +188,7 @@ export function useDataImport() { error: e instanceof Error ? e.message : String(e), }); } - }, [state.parsedData, state.importType]); + }, [state.parsedData, state.importType, state.filePath]); const reset = useCallback(() => dispatch({ type: "RESET" }), []); diff --git a/src/services/dataExportService.ts b/src/services/dataExportService.ts index 70b9c00..ba050e6 100644 --- a/src/services/dataExportService.ts +++ b/src/services/dataExportService.ts @@ -254,13 +254,15 @@ export async function importCategoriesOnly(data: ExportEnvelope["data"]): Promis } export async function importTransactionsWithCategories( - data: ExportEnvelope["data"] + data: ExportEnvelope["data"], + filename: string ): Promise { const db = await getDb(); // Wipe everything await db.execute("DELETE FROM transactions"); await db.execute("DELETE FROM imported_files"); + await db.execute("DELETE FROM import_sources"); await db.execute("DELETE FROM keywords"); await db.execute("DELETE FROM suppliers"); await db.execute("DELETE FROM categories"); @@ -308,12 +310,28 @@ export async function importTransactionsWithCategories( } } - // Re-insert transactions + // Create tracking records for import history + const sourceResult = await db.execute( + `INSERT INTO import_sources (name, description, date_format, delimiter, encoding, column_mapping, skip_lines) + VALUES ($1, $2, $3, $4, $5, $6, $7)`, + ["Data Import", "Imported from settings", "%Y-%m-%d", ",", "utf-8", "{}", 0] + ); + const sourceId = sourceResult.lastInsertId; + + const txCount = data.transactions?.length ?? 0; + const fileResult = await db.execute( + `INSERT INTO imported_files (source_id, filename, file_hash, row_count, status) + VALUES ($1, $2, $3, $4, $5)`, + [sourceId, filename, `data-import-${Date.now()}`, txCount, "completed"] + ); + const fileId = fileResult.lastInsertId; + + // Re-insert transactions linked to the import if (data.transactions) { for (const tx of data.transactions) { await db.execute( - `INSERT INTO transactions (date, description, amount, category_id, original_description, notes, is_manually_categorized, is_split, parent_transaction_id) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, + `INSERT INTO transactions (date, description, amount, category_id, original_description, notes, is_manually_categorized, is_split, parent_transaction_id, source_id, file_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [ tx.date, tx.description, @@ -324,6 +342,8 @@ export async function importTransactionsWithCategories( tx.is_manually_categorized, tx.is_split, tx.parent_transaction_id, + sourceId, + fileId, ] ); } @@ -331,20 +351,38 @@ export async function importTransactionsWithCategories( } export async function importTransactionsOnly( - data: ExportEnvelope["data"] + data: ExportEnvelope["data"], + filename: string ): Promise { const db = await getDb(); // Wipe transactions and import history await db.execute("DELETE FROM transactions"); await db.execute("DELETE FROM imported_files"); + await db.execute("DELETE FROM import_sources"); - // Re-insert transactions + // Create tracking records for import history + const sourceResult = await db.execute( + `INSERT INTO import_sources (name, description, date_format, delimiter, encoding, column_mapping, skip_lines) + VALUES ($1, $2, $3, $4, $5, $6, $7)`, + ["Data Import", "Imported from settings", "%Y-%m-%d", ",", "utf-8", "{}", 0] + ); + const sourceId = sourceResult.lastInsertId; + + const txCount = data.transactions?.length ?? 0; + const fileResult = await db.execute( + `INSERT INTO imported_files (source_id, filename, file_hash, row_count, status) + VALUES ($1, $2, $3, $4, $5)`, + [sourceId, filename, `data-import-${Date.now()}`, txCount, "completed"] + ); + const fileId = fileResult.lastInsertId; + + // Re-insert transactions linked to the import if (data.transactions) { for (const tx of data.transactions) { await db.execute( - `INSERT INTO transactions (date, description, amount, category_id, original_description, notes, is_manually_categorized, is_split, parent_transaction_id) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, + `INSERT INTO transactions (date, description, amount, category_id, original_description, notes, is_manually_categorized, is_split, parent_transaction_id, source_id, file_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [ tx.date, tx.description, @@ -355,6 +393,8 @@ export async function importTransactionsOnly( tx.is_manually_categorized, tx.is_split, tx.parent_transaction_id, + sourceId, + fileId, ] ); }