From ceef9cf26fbc70dfc487fdca563d1322df25f812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=CC=81ctor=20Falco=CC=81n?= Date: Sat, 18 Jul 2026 13:00:33 +0200 Subject: [PATCH] refactor(import): merge storage helpers, keep file preview off the network path - Merge the two ~identical import-config storage modules into one (transaction + balance share the same HTTP logic). - Show the parsed file immediately and load the saved per-account config off the critical path, so a slow or hanging request never blocks the preview or Next button; a stale load can't clobber a newer file. - Drop the dead $hidden on AccountImportConfig (the controller returns the raw config blob, never the serialized model). --- app/Models/AccountImportConfig.php | 8 --- .../accounts/import-balances-drawer.tsx | 60 +++++++++++-------- .../import-transactions-drawer.tsx | 52 +++++++++------- .../js/lib/balance-import-config-storage.ts | 49 --------------- resources/js/lib/import-config-storage.ts | 57 ++++++++++++++---- 5 files changed, 109 insertions(+), 117 deletions(-) delete mode 100644 resources/js/lib/balance-import-config-storage.ts diff --git a/app/Models/AccountImportConfig.php b/app/Models/AccountImportConfig.php index f1185fa0..883305f2 100644 --- a/app/Models/AccountImportConfig.php +++ b/app/Models/AccountImportConfig.php @@ -20,14 +20,6 @@ class AccountImportConfig extends Model 'config', ]; - /** @var list */ - protected $hidden = [ - 'id', - 'account_id', - 'created_at', - 'updated_at', - ]; - protected function casts(): array { return [ diff --git a/resources/js/components/accounts/import-balances-drawer.tsx b/resources/js/components/accounts/import-balances-drawer.tsx index f84b92df..44491582 100644 --- a/resources/js/components/accounts/import-balances-drawer.tsx +++ b/resources/js/components/accounts/import-balances-drawer.tsx @@ -8,10 +8,6 @@ import { DrawerTitle, } from '@/components/ui/drawer'; import { Progress } from '@/components/ui/progress'; -import { - loadBalanceImportConfig, - saveBalanceImportConfig, -} from '@/lib/balance-import-config-storage'; import { getCsrfToken } from '@/lib/csrf'; import { detectDateFormat, @@ -19,6 +15,10 @@ import { parseDate, parseFile, } from '@/lib/file-parser'; +import { + loadBalanceImportConfig, + saveBalanceImportConfig, +} from '@/lib/import-config-storage'; import { type SharedData } from '@/types'; import { supportsInvestedAmount, type Account } from '@/types/account'; import { @@ -279,13 +279,23 @@ export function ImportBalancesDrawer({ } } - let finalMapping = autoMapping; - let finalDateFormat = detectedFormat; + // Show the parsed file immediately with auto-detected columns; the + // saved per-account config is fetched off the critical path so a + // slow or hanging network never blocks the preview or Next button. + setState((prev) => ({ + ...prev, + file, + parsedData: data, + columnHeaders: headers, + columnOptions, + columnMapping: autoMapping, + dateFormat: detectedFormat, + dateFormatDetected: formatDetected, + })); - if (state.selectedAccountId) { - const savedConfig = await loadBalanceImportConfig( - state.selectedAccountId, - ); + const accountId = state.selectedAccountId; + if (accountId) { + const savedConfig = await loadBalanceImportConfig(accountId); if (savedConfig) { const isValidMapping = ( @@ -300,26 +310,24 @@ export function ImportBalancesDrawer({ }; if (isValidMapping(savedConfig.columnMapping)) { - finalMapping = savedConfig.columnMapping; - finalDateFormat = savedConfig.dateFormat; - // Keep the saved format as the default, but still show - // the selector when the dates are ambiguous so a + // Only apply if this file is still the selected one, so a + // slow load can't clobber a file picked afterwards. Keep + // the saved format as the default, but still show the + // selector when the dates are ambiguous so a // previously-saved wrong format can be corrected. - formatDetected = !formatAmbiguous; + setState((prev) => + prev.file === file + ? { + ...prev, + columnMapping: savedConfig.columnMapping, + dateFormat: savedConfig.dateFormat, + dateFormatDetected: !formatAmbiguous, + } + : prev, + ); } } } - - setState((prev) => ({ - ...prev, - file, - parsedData: data, - columnHeaders: headers, - columnOptions, - columnMapping: finalMapping, - dateFormat: finalDateFormat, - dateFormatDetected: formatDetected, - })); } catch (err) { setError( err instanceof Error ? err.message : 'Failed to parse file', diff --git a/resources/js/components/transactions/import-transactions-drawer.tsx b/resources/js/components/transactions/import-transactions-drawer.tsx index bb85bfee..18ceae2f 100644 --- a/resources/js/components/transactions/import-transactions-drawer.tsx +++ b/resources/js/components/transactions/import-transactions-drawer.tsx @@ -223,13 +223,23 @@ export function ImportTransactionsDrawer({ } } - let finalMapping = autoMapping; - let finalDateFormat = detectedFormat; + // Show the parsed file immediately with auto-detected columns; the + // saved per-account config is fetched off the critical path so a + // slow or hanging network never blocks the preview or Next button. + setState((prev) => ({ + ...prev, + file, + parsedData: data, + columnHeaders: headers, + columnOptions, + columnMapping: autoMapping, + dateFormat: detectedFormat, + dateFormatDetected: formatDetected, + })); - if (state.selectedAccountId) { - const savedConfig = await loadImportConfig( - state.selectedAccountId, - ); + const accountId = state.selectedAccountId; + if (accountId) { + const savedConfig = await loadImportConfig(accountId); if (savedConfig) { const isValidMapping = ( @@ -249,26 +259,24 @@ export function ImportTransactionsDrawer({ }; if (isValidMapping(savedConfig.columnMapping)) { - finalMapping = savedConfig.columnMapping; - finalDateFormat = savedConfig.dateFormat; - // Keep the saved format as the default, but still show - // the selector when the dates are ambiguous so a + // Only apply if this file is still the selected one, so a + // slow load can't clobber a file picked afterwards. Keep + // the saved format as the default, but still show the + // selector when the dates are ambiguous so a // previously-saved wrong format can be corrected. - formatDetected = !formatAmbiguous; + setState((prev) => + prev.file === file + ? { + ...prev, + columnMapping: savedConfig.columnMapping, + dateFormat: savedConfig.dateFormat, + dateFormatDetected: !formatAmbiguous, + } + : prev, + ); } } } - - setState((prev) => ({ - ...prev, - file, - parsedData: data, - columnHeaders: headers, - columnOptions, - columnMapping: finalMapping, - dateFormat: finalDateFormat, - dateFormatDetected: formatDetected, - })); } catch (err) { setError( err instanceof Error ? err.message : 'Failed to parse file', diff --git a/resources/js/lib/balance-import-config-storage.ts b/resources/js/lib/balance-import-config-storage.ts deleted file mode 100644 index 8643238f..00000000 --- a/resources/js/lib/balance-import-config-storage.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { BalanceColumnMapping } from '@/types/balance-import'; -import { DateFormat } from '@/types/import'; -import type { UUID } from '@/types/uuid'; -import axios from 'axios'; - -interface BalanceImportConfig { - columnMapping: BalanceColumnMapping; - dateFormat: DateFormat; -} - -function configUrl(accountId: UUID): string { - return `/api/accounts/${accountId}/import-config`; -} - -export async function saveBalanceImportConfig( - accountId: UUID, - config: BalanceImportConfig, -): Promise { - try { - await axios.put(configUrl(accountId), { - type: 'balance', - config, - }); - } catch (error) { - console.error('Failed to save balance import configuration:', error); - } -} - -export async function loadBalanceImportConfig( - accountId: UUID, -): Promise { - try { - const { data } = await axios.get<{ data: BalanceImportConfig | null }>( - configUrl(accountId), - { params: { type: 'balance' } }, - ); - - const config = data.data; - - if (!config || !config.columnMapping || !config.dateFormat) { - return null; - } - - return config; - } catch (error) { - console.error('Failed to load balance import configuration:', error); - return null; - } -} diff --git a/resources/js/lib/import-config-storage.ts b/resources/js/lib/import-config-storage.ts index eed5e8c5..57848a5d 100644 --- a/resources/js/lib/import-config-storage.ts +++ b/resources/js/lib/import-config-storage.ts @@ -1,3 +1,4 @@ +import type { BalanceColumnMapping } from '@/types/balance-import'; import { type ColumnMapping, DateFormat } from '@/types/import'; import { type UUID } from '@/types/uuid'; import axios from 'axios'; @@ -7,31 +8,37 @@ interface ImportConfig { dateFormat: DateFormat; } +interface BalanceImportConfig { + columnMapping: BalanceColumnMapping; + dateFormat: DateFormat; +} + +type ImportConfigType = 'transaction' | 'balance'; + function configUrl(accountId: UUID): string { return `/api/accounts/${accountId}/import-config`; } -export async function saveImportConfig( +async function saveConfig( accountId: UUID, - config: ImportConfig, + type: ImportConfigType, + config: ImportConfig | BalanceImportConfig, ): Promise { try { - await axios.put(configUrl(accountId), { - type: 'transaction', - config, - }); + await axios.put(configUrl(accountId), { type, config }); } catch (error) { - console.error('Failed to save import configuration:', error); + console.error(`Failed to save ${type} import configuration:`, error); } } -export async function loadImportConfig( +async function loadConfig( accountId: UUID, -): Promise { + type: ImportConfigType, +): Promise { try { - const { data } = await axios.get<{ data: ImportConfig | null }>( + const { data } = await axios.get<{ data: T | null }>( configUrl(accountId), - { params: { type: 'transaction' } }, + { params: { type } }, ); const config = data.data; @@ -42,7 +49,33 @@ export async function loadImportConfig( return config; } catch (error) { - console.error('Failed to load import configuration:', error); + console.error(`Failed to load ${type} import configuration:`, error); return null; } } + +export function saveImportConfig( + accountId: UUID, + config: ImportConfig, +): Promise { + return saveConfig(accountId, 'transaction', config); +} + +export function loadImportConfig( + accountId: UUID, +): Promise { + return loadConfig(accountId, 'transaction'); +} + +export function saveBalanceImportConfig( + accountId: UUID, + config: BalanceImportConfig, +): Promise { + return saveConfig(accountId, 'balance', config); +} + +export function loadBalanceImportConfig( + accountId: UUID, +): Promise { + return loadConfig(accountId, 'balance'); +}