diff --git a/src/components/import/SourceConfigPanel.tsx b/src/components/import/SourceConfigPanel.tsx index 56eac8c..59ebb82 100644 --- a/src/components/import/SourceConfigPanel.tsx +++ b/src/components/import/SourceConfigPanel.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { Wand2, Check, Save, X } from "lucide-react"; +import { Wand2, Check, Save, RefreshCw, X } from "lucide-react"; import type { ScannedSource, ScannedFile, @@ -24,7 +24,9 @@ interface SourceConfigPanelProps { onAutoDetect: () => void; onSaveAsTemplate: (name: string) => void; onApplyTemplate: (id: number) => void; + onUpdateTemplate: () => void; onDeleteTemplate: (id: number) => void; + selectedTemplateId: number | null; isLoading?: boolean; } @@ -41,7 +43,9 @@ export default function SourceConfigPanel({ onAutoDetect, onSaveAsTemplate, onApplyTemplate, + onUpdateTemplate, onDeleteTemplate, + selectedTemplateId, isLoading, }: SourceConfigPanelProps) { const { t } = useTranslation(); @@ -79,7 +83,7 @@ export default function SourceConfigPanel({ {t("import.config.loadTemplate")} + {selectedTemplateId && ( + + )} {configTemplates.length > 0 && (
{configTemplates.map((tpl) => ( diff --git a/src/hooks/useImportWizard.ts b/src/hooks/useImportWizard.ts index 1d0071b..4602428 100644 --- a/src/hooks/useImportWizard.ts +++ b/src/hooks/useImportWizard.ts @@ -37,6 +37,7 @@ import { categorizeBatch } from "../services/categorizationService"; import { getAllTemplates, createTemplate, + updateTemplate, deleteTemplate as deleteTemplateService, } from "../services/importConfigTemplateService"; import { parseDate } from "../utils/dateParser"; @@ -65,6 +66,7 @@ interface WizardState { configuredSourceNames: Set; importedFilesBySource: Map>; configTemplates: ImportConfigTemplate[]; + selectedTemplateId: number | null; } type WizardAction = @@ -85,6 +87,7 @@ type WizardAction = | { type: "SET_IMPORT_PROGRESS"; payload: { current: number; total: number; file: string } } | { type: "SET_CONFIGURED_SOURCES"; payload: { names: Set; files: Map> } } | { type: "SET_CONFIG_TEMPLATES"; payload: ImportConfigTemplate[] } + | { type: "SET_SELECTED_TEMPLATE_ID"; payload: number | null } | { type: "RESET" }; const defaultConfig: SourceConfig = { @@ -118,6 +121,7 @@ const initialState: WizardState = { configuredSourceNames: new Set(), importedFilesBySource: new Map(), configTemplates: [], + selectedTemplateId: null, }; function reducer(state: WizardState, action: WizardAction): WizardState { @@ -182,6 +186,8 @@ function reducer(state: WizardState, action: WizardAction): WizardState { }; case "SET_CONFIG_TEMPLATES": return { ...state, configTemplates: action.payload }; + case "SET_SELECTED_TEMPLATE_ID": + return { ...state, selectedTemplateId: action.payload }; case "RESET": return { ...initialState, @@ -291,6 +297,7 @@ export function useImportWizard() { dispatch({ type: "SET_SELECTED_SOURCE", payload: sortedSource }); dispatch({ type: "SET_SELECTED_FILES", payload: newFiles }); + dispatch({ type: "SET_SELECTED_TEMPLATE_ID", payload: null }); // Check if this source already has config in DB const existing = await getSourceByName(source.folder_name); @@ -945,6 +952,7 @@ export function useImportWizard() { hasHeader: !!template.has_header, }; dispatch({ type: "SET_SOURCE_CONFIG", payload: newConfig }); + dispatch({ type: "SET_SELECTED_TEMPLATE_ID", payload: templateId }); // Reload headers with new config if (state.selectedFiles.length > 0) { @@ -958,11 +966,34 @@ export function useImportWizard() { } }, [state.configTemplates, state.sourceConfig.name, state.selectedFiles, loadHeadersWithConfig]); - const deleteConfigTemplate = useCallback(async (id: number) => { - await deleteTemplateService(id); + const updateConfigTemplate = useCallback(async () => { + if (!state.selectedTemplateId) return; + const template = state.configTemplates.find((t) => t.id === state.selectedTemplateId); + if (!template) return; + const config = state.sourceConfig; + await updateTemplate(state.selectedTemplateId, { + name: template.name, + delimiter: config.delimiter, + encoding: config.encoding, + date_format: config.dateFormat, + skip_lines: config.skipLines, + has_header: config.hasHeader ? 1 : 0, + column_mapping: JSON.stringify(config.columnMapping), + amount_mode: config.amountMode, + sign_convention: config.signConvention, + }); const templates = await getAllTemplates(); dispatch({ type: "SET_CONFIG_TEMPLATES", payload: templates }); - }, []); + }, [state.selectedTemplateId, state.configTemplates, state.sourceConfig]); + + const deleteConfigTemplate = useCallback(async (id: number) => { + await deleteTemplateService(id); + if (state.selectedTemplateId === id) { + dispatch({ type: "SET_SELECTED_TEMPLATE_ID", payload: null }); + } + const templates = await getAllTemplates(); + dispatch({ type: "SET_CONFIG_TEMPLATES", payload: templates }); + }, [state.selectedTemplateId]); return { state, @@ -981,6 +1012,7 @@ export function useImportWizard() { autoDetectConfig, saveConfigAsTemplate, applyConfigTemplate, + updateConfigTemplate, deleteConfigTemplate, toggleDuplicateRow: (index: number) => dispatch({ type: "TOGGLE_DUPLICATE_ROW", payload: index }), diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index a38044a..9a37b7f 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -90,7 +90,8 @@ "templateName": "Template name", "templateSaved": "Template saved", "deleteTemplate": "Delete template", - "noTemplates": "No templates saved" + "noTemplates": "No templates saved", + "updateTemplate": "Update template" }, "preview": { "title": "Data Preview", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 28e58bf..addba46 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -90,7 +90,8 @@ "templateName": "Nom du modèle", "templateSaved": "Modèle sauvegardé", "deleteTemplate": "Supprimer le modèle", - "noTemplates": "Aucun modèle sauvegardé" + "noTemplates": "Aucun modèle sauvegardé", + "updateTemplate": "Mettre à jour le modèle" }, "preview": { "title": "Aperçu des données", diff --git a/src/pages/ImportPage.tsx b/src/pages/ImportPage.tsx index 6a6ea49..4ce7744 100644 --- a/src/pages/ImportPage.tsx +++ b/src/pages/ImportPage.tsx @@ -32,6 +32,7 @@ export default function ImportPage() { autoDetectConfig, saveConfigAsTemplate, applyConfigTemplate, + updateConfigTemplate, deleteConfigTemplate, toggleDuplicateRow, setSkipAllDuplicates, @@ -99,7 +100,9 @@ export default function ImportPage() { onAutoDetect={autoDetectConfig} onSaveAsTemplate={saveConfigAsTemplate} onApplyTemplate={applyConfigTemplate} + onUpdateTemplate={updateConfigTemplate} onDeleteTemplate={deleteConfigTemplate} + selectedTemplateId={state.selectedTemplateId} isLoading={state.isLoading} />
diff --git a/src/services/importConfigTemplateService.ts b/src/services/importConfigTemplateService.ts index 586bc1d..29d86b7 100644 --- a/src/services/importConfigTemplateService.ts +++ b/src/services/importConfigTemplateService.ts @@ -30,6 +30,31 @@ export async function createTemplate( return result.lastInsertId as number; } +export async function updateTemplate( + id: number, + template: Omit +): Promise { + const db = await getDb(); + await db.execute( + `UPDATE import_config_templates + SET name=$1, delimiter=$2, encoding=$3, date_format=$4, skip_lines=$5, + has_header=$6, column_mapping=$7, amount_mode=$8, sign_convention=$9 + WHERE id=$10`, + [ + template.name, + template.delimiter, + template.encoding, + template.date_format, + template.skip_lines, + template.has_header, + template.column_mapping, + template.amount_mode, + template.sign_convention, + id, + ] + ); +} + export async function deleteTemplate(id: number): Promise { const db = await getDb(); await db.execute("DELETE FROM import_config_templates WHERE id = $1", [id]);