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 <noreply@anthropic.com>
This commit is contained in:
Le-King-Fu 2026-02-15 13:38:51 +00:00
parent 172be36f1d
commit e23e559ee3
2 changed files with 56 additions and 13 deletions

View file

@ -33,6 +33,7 @@ type ImportAction =
| { type: "NEEDS_PASSWORD"; filePath: string } | { type: "NEEDS_PASSWORD"; filePath: string }
| { | {
type: "CONFIRMING"; type: "CONFIRMING";
filePath: string;
summary: ImportSummary; summary: ImportSummary;
data: ExportEnvelope["data"]; data: ExportEnvelope["data"];
importType: ExportEnvelope["export_type"]; importType: ExportEnvelope["export_type"];
@ -61,6 +62,7 @@ function reducer(state: ImportState, action: ImportAction): ImportState {
return { return {
...state, ...state,
status: "confirming", status: "confirming",
filePath: action.filePath,
summary: action.summary, summary: action.summary,
parsedData: action.data, parsedData: action.data,
importType: action.importType, importType: action.importType,
@ -132,7 +134,7 @@ export function useDataImport() {
}); });
const { summary, data, importType } = parseContent(content, filePath); const { summary, data, importType } = parseContent(content, filePath);
dispatch({ type: "CONFIRMING", summary, data, importType }); dispatch({ type: "CONFIRMING", filePath, summary, data, importType });
} catch (e) { } catch (e) {
dispatch({ dispatch({
type: "IMPORT_ERROR", type: "IMPORT_ERROR",
@ -152,7 +154,7 @@ export function useDataImport() {
}); });
const { summary, data, importType } = parseContent(content, state.filePath); 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) { } catch (e) {
dispatch({ dispatch({
type: "IMPORT_ERROR", type: "IMPORT_ERROR",
@ -166,16 +168,17 @@ export function useDataImport() {
const executeImport = useCallback(async () => { const executeImport = useCallback(async () => {
if (!state.parsedData || !state.importType) return; if (!state.parsedData || !state.importType) return;
dispatch({ type: "IMPORT_START" }); dispatch({ type: "IMPORT_START" });
const filename = state.filePath?.split(/[/\\]/).pop() ?? "unknown";
try { try {
switch (state.importType) { switch (state.importType) {
case "categories_only": case "categories_only":
await importCategoriesOnly(state.parsedData); await importCategoriesOnly(state.parsedData);
break; break;
case "transactions_with_categories": case "transactions_with_categories":
await importTransactionsWithCategories(state.parsedData); await importTransactionsWithCategories(state.parsedData, filename);
break; break;
case "transactions_only": case "transactions_only":
await importTransactionsOnly(state.parsedData); await importTransactionsOnly(state.parsedData, filename);
break; break;
} }
dispatch({ type: "IMPORT_SUCCESS" }); dispatch({ type: "IMPORT_SUCCESS" });
@ -185,7 +188,7 @@ export function useDataImport() {
error: e instanceof Error ? e.message : String(e), error: e instanceof Error ? e.message : String(e),
}); });
} }
}, [state.parsedData, state.importType]); }, [state.parsedData, state.importType, state.filePath]);
const reset = useCallback(() => dispatch({ type: "RESET" }), []); const reset = useCallback(() => dispatch({ type: "RESET" }), []);

View file

@ -254,13 +254,15 @@ export async function importCategoriesOnly(data: ExportEnvelope["data"]): Promis
} }
export async function importTransactionsWithCategories( export async function importTransactionsWithCategories(
data: ExportEnvelope["data"] data: ExportEnvelope["data"],
filename: string
): Promise<void> { ): Promise<void> {
const db = await getDb(); const db = await getDb();
// Wipe everything // Wipe everything
await db.execute("DELETE FROM transactions"); await db.execute("DELETE FROM transactions");
await db.execute("DELETE FROM imported_files"); 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 keywords");
await db.execute("DELETE FROM suppliers"); await db.execute("DELETE FROM suppliers");
await db.execute("DELETE FROM categories"); 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) { if (data.transactions) {
for (const tx of data.transactions) { for (const tx of data.transactions) {
await db.execute( await db.execute(
`INSERT INTO transactions (date, description, amount, category_id, original_description, notes, is_manually_categorized, is_split, parent_transaction_id) `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)`, VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
[ [
tx.date, tx.date,
tx.description, tx.description,
@ -324,6 +342,8 @@ export async function importTransactionsWithCategories(
tx.is_manually_categorized, tx.is_manually_categorized,
tx.is_split, tx.is_split,
tx.parent_transaction_id, tx.parent_transaction_id,
sourceId,
fileId,
] ]
); );
} }
@ -331,20 +351,38 @@ export async function importTransactionsWithCategories(
} }
export async function importTransactionsOnly( export async function importTransactionsOnly(
data: ExportEnvelope["data"] data: ExportEnvelope["data"],
filename: string
): Promise<void> { ): Promise<void> {
const db = await getDb(); const db = await getDb();
// Wipe transactions and import history // Wipe transactions and import history
await db.execute("DELETE FROM transactions"); await db.execute("DELETE FROM transactions");
await db.execute("DELETE FROM imported_files"); 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) { if (data.transactions) {
for (const tx of data.transactions) { for (const tx of data.transactions) {
await db.execute( await db.execute(
`INSERT INTO transactions (date, description, amount, category_id, original_description, notes, is_manually_categorized, is_split, parent_transaction_id) `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)`, VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
[ [
tx.date, tx.date,
tx.description, tx.description,
@ -355,6 +393,8 @@ export async function importTransactionsOnly(
tx.is_manually_categorized, tx.is_manually_categorized,
tx.is_split, tx.is_split,
tx.parent_transaction_id, tx.parent_transaction_id,
sourceId,
fileId,
] ]
); );
} }