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:
parent
172be36f1d
commit
e23e559ee3
2 changed files with 56 additions and 13 deletions
|
|
@ -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" }), []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
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<void> {
|
||||
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,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue