Simpl-Resultat/src/hooks/useImportHistory.ts
Le-King-Fu 3506c2c87e feat: add import history panel with delete and cross-source duplicate detection
Make duplicate file detection cross-source by removing sourceId from
existsByHash query. Add import history table below source list with
per-import and delete-all functionality.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 01:29:23 +00:00

116 lines
3.3 KiB
TypeScript

import { useReducer, useCallback, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import type { ImportedFileWithSource } from "../shared/types";
import {
getAllImportedFiles,
deleteImportWithTransactions,
deleteAllImportsWithTransactions,
} from "../services/importedFileService";
interface ImportHistoryState {
files: ImportedFileWithSource[];
isLoading: boolean;
isDeleting: boolean;
error: string | null;
}
type ImportHistoryAction =
| { type: "SET_LOADING"; payload: boolean }
| { type: "SET_DELETING"; payload: boolean }
| { type: "SET_ERROR"; payload: string | null }
| { type: "SET_FILES"; payload: ImportedFileWithSource[] };
const initialState: ImportHistoryState = {
files: [],
isLoading: false,
isDeleting: false,
error: null,
};
function reducer(
state: ImportHistoryState,
action: ImportHistoryAction
): ImportHistoryState {
switch (action.type) {
case "SET_LOADING":
return { ...state, isLoading: action.payload };
case "SET_DELETING":
return { ...state, isDeleting: action.payload };
case "SET_ERROR":
return { ...state, error: action.payload };
case "SET_FILES":
return { ...state, files: action.payload };
default:
return state;
}
}
export function useImportHistory(onChanged?: () => void) {
const [state, dispatch] = useReducer(reducer, initialState);
const fetchIdRef = useRef(0);
const { t } = useTranslation();
const loadHistory = useCallback(async () => {
const fetchId = ++fetchIdRef.current;
dispatch({ type: "SET_LOADING", payload: true });
dispatch({ type: "SET_ERROR", payload: null });
try {
const files = await getAllImportedFiles();
if (fetchId !== fetchIdRef.current) return;
dispatch({ type: "SET_FILES", payload: files });
} catch (err) {
if (fetchId !== fetchIdRef.current) return;
dispatch({ type: "SET_ERROR", payload: String(err) });
} finally {
if (fetchId === fetchIdRef.current) {
dispatch({ type: "SET_LOADING", payload: false });
}
}
}, []);
const handleDelete = useCallback(
async (fileId: number, rowCount: number) => {
const ok = confirm(
t("import.history.deleteConfirm", { count: rowCount })
);
if (!ok) return;
dispatch({ type: "SET_DELETING", payload: true });
try {
await deleteImportWithTransactions(fileId);
await loadHistory();
onChanged?.();
} catch (err) {
dispatch({ type: "SET_ERROR", payload: String(err) });
} finally {
dispatch({ type: "SET_DELETING", payload: false });
}
},
[loadHistory, onChanged, t]
);
const handleDeleteAll = useCallback(async () => {
const ok = confirm(t("import.history.deleteAllConfirm"));
if (!ok) return;
dispatch({ type: "SET_DELETING", payload: true });
try {
await deleteAllImportsWithTransactions();
await loadHistory();
onChanged?.();
} catch (err) {
dispatch({ type: "SET_ERROR", payload: String(err) });
} finally {
dispatch({ type: "SET_DELETING", payload: false });
}
}, [loadHistory, onChanged, t]);
useEffect(() => {
loadHistory();
}, [loadHistory]);
return {
state,
loadHistory,
handleDelete,
handleDeleteAll,
};
}