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).
This commit is contained in:
parent
b6c759cfd6
commit
ceef9cf26f
|
|
@ -20,14 +20,6 @@ class AccountImportConfig extends Model
|
|||
'config',
|
||||
];
|
||||
|
||||
/** @var list<string> */
|
||||
protected $hidden = [
|
||||
'id',
|
||||
'account_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
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<BalanceImportConfig | null> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<void> {
|
||||
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<T extends ImportConfig | BalanceImportConfig>(
|
||||
accountId: UUID,
|
||||
): Promise<ImportConfig | null> {
|
||||
type: ImportConfigType,
|
||||
): Promise<T | null> {
|
||||
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<void> {
|
||||
return saveConfig(accountId, 'transaction', config);
|
||||
}
|
||||
|
||||
export function loadImportConfig(
|
||||
accountId: UUID,
|
||||
): Promise<ImportConfig | null> {
|
||||
return loadConfig<ImportConfig>(accountId, 'transaction');
|
||||
}
|
||||
|
||||
export function saveBalanceImportConfig(
|
||||
accountId: UUID,
|
||||
config: BalanceImportConfig,
|
||||
): Promise<void> {
|
||||
return saveConfig(accountId, 'balance', config);
|
||||
}
|
||||
|
||||
export function loadBalanceImportConfig(
|
||||
accountId: UUID,
|
||||
): Promise<BalanceImportConfig | null> {
|
||||
return loadConfig<BalanceImportConfig>(accountId, 'balance');
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue