152 lines
4.7 KiB
TypeScript
152 lines
4.7 KiB
TypeScript
import { browser } from '$app/environment';
|
|
import type { CsvImportProfile, CsvImportTab } from '$lib/csv-import-session';
|
|
|
|
const VALID_PROFILES: CsvImportProfile[] = [
|
|
'customs_brokers',
|
|
'clients_providers',
|
|
'exchange_rates',
|
|
'american_fractions',
|
|
'pedimentos',
|
|
'material_classes',
|
|
'vehicles',
|
|
'drivers',
|
|
'trailers',
|
|
'transporters',
|
|
'part_numbers',
|
|
'boms',
|
|
'exportacion',
|
|
'imports'
|
|
];
|
|
|
|
const VALID_TABS: CsvImportTab[] = ['catalogos', 'transportes', 'importacion', 'exportacion'];
|
|
|
|
export const CSV_IMPORT_PENDING_KEY = 'anexo76_csv_pending_v1';
|
|
|
|
export const CSV_IMPORT_PENDING_CHANGED = 'csvImportPendingChanged';
|
|
|
|
const MAX_ITEMS = 20;
|
|
|
|
export interface CsvImportPendingEntry {
|
|
companyId: number;
|
|
jobId: string;
|
|
profile: CsvImportProfile;
|
|
activeModelTarget: string | null;
|
|
activeTab: CsvImportTab;
|
|
label?: string | null;
|
|
savedAt: string;
|
|
totalRows?: number;
|
|
validRows?: number;
|
|
}
|
|
|
|
interface PendingStoreV1 {
|
|
v: 1;
|
|
items: CsvImportPendingEntry[];
|
|
}
|
|
|
|
function notifyPendingChanged() {
|
|
if (browser) {
|
|
window.dispatchEvent(new CustomEvent(CSV_IMPORT_PENDING_CHANGED));
|
|
}
|
|
}
|
|
|
|
function parseStore(raw: string | null): PendingStoreV1 {
|
|
if (!raw) return { v: 1, items: [] };
|
|
try {
|
|
const data = JSON.parse(raw) as Partial<PendingStoreV1>;
|
|
if (data.v !== 1 || !Array.isArray(data.items)) return { v: 1, items: [] };
|
|
return { v: 1, items: data.items.filter(isValidEntry) };
|
|
} catch {
|
|
return { v: 1, items: [] };
|
|
}
|
|
}
|
|
|
|
function isValidEntry(x: unknown): x is CsvImportPendingEntry {
|
|
if (!x || typeof x !== 'object') return false;
|
|
const o = x as Record<string, unknown>;
|
|
return (
|
|
typeof o.companyId === 'number' &&
|
|
typeof o.jobId === 'string' &&
|
|
typeof o.profile === 'string' &&
|
|
VALID_PROFILES.includes(o.profile as CsvImportProfile) &&
|
|
typeof o.savedAt === 'string' &&
|
|
(o.activeModelTarget === null || typeof o.activeModelTarget === 'string') &&
|
|
typeof o.activeTab === 'string' &&
|
|
VALID_TABS.includes(o.activeTab as CsvImportTab)
|
|
);
|
|
}
|
|
|
|
function writeStore(store: PendingStoreV1, notify = true) {
|
|
if (!browser) return;
|
|
try {
|
|
localStorage.setItem(CSV_IMPORT_PENDING_KEY, JSON.stringify(store));
|
|
if (notify) notifyPendingChanged();
|
|
} catch {
|
|
//
|
|
}
|
|
}
|
|
|
|
export function readCsvImportPendingStore(): PendingStoreV1 {
|
|
if (!browser) return { v: 1, items: [] };
|
|
return parseStore(localStorage.getItem(CSV_IMPORT_PENDING_KEY));
|
|
}
|
|
|
|
export function listCsvImportPendingForCompany(companyId: number | undefined): CsvImportPendingEntry[] {
|
|
if (companyId === undefined) return [];
|
|
const { items } = readCsvImportPendingStore();
|
|
return items
|
|
.filter((i) => i.companyId === companyId)
|
|
.sort((a, b) => (a.savedAt < b.savedAt ? 1 : -1));
|
|
}
|
|
|
|
export function countCsvImportPendingForCompany(companyId: number | undefined): number {
|
|
return listCsvImportPendingForCompany(companyId).length;
|
|
}
|
|
|
|
/** Inserta o actualiza por jobId; mantiene como máximo MAX_ITEMS (más recientes primero). */
|
|
export function upsertCsvImportPending(partial: Omit<CsvImportPendingEntry, 'savedAt'> & { savedAt?: string }): void {
|
|
if (!browser) return;
|
|
const store = readCsvImportPendingStore();
|
|
const now = partial.savedAt ?? new Date().toISOString();
|
|
const next: CsvImportPendingEntry = {
|
|
companyId: partial.companyId,
|
|
jobId: partial.jobId,
|
|
profile: partial.profile,
|
|
activeModelTarget: partial.activeModelTarget,
|
|
activeTab: partial.activeTab,
|
|
savedAt: now,
|
|
...(partial.label !== undefined && partial.label !== null && String(partial.label).trim()
|
|
? { label: String(partial.label).trim() }
|
|
: {}),
|
|
...(typeof partial.totalRows === 'number' ? { totalRows: partial.totalRows } : {}),
|
|
...(typeof partial.validRows === 'number' ? { validRows: partial.validRows } : {})
|
|
};
|
|
const without = store.items.filter((i) => i.jobId !== next.jobId);
|
|
const merged = [next, ...without].sort((a, b) => (a.savedAt < b.savedAt ? 1 : -1)).slice(0, MAX_ITEMS);
|
|
writeStore({ v: 1, items: merged });
|
|
}
|
|
|
|
export function removeCsvImportPending(jobId: string): void {
|
|
if (!browser) return;
|
|
const store = readCsvImportPendingStore();
|
|
const filtered = store.items.filter((i) => i.jobId !== jobId);
|
|
if (filtered.length === store.items.length) return;
|
|
writeStore({ v: 1, items: filtered });
|
|
}
|
|
|
|
export function updateCsvImportPendingSnapshot(
|
|
jobId: string,
|
|
patch: Pick<CsvImportPendingEntry, 'totalRows' | 'validRows'>
|
|
): void {
|
|
if (!browser) return;
|
|
const store = readCsvImportPendingStore();
|
|
const idx = store.items.findIndex((i) => i.jobId === jobId);
|
|
if (idx < 0) return;
|
|
const cur = store.items[idx];
|
|
store.items[idx] = {
|
|
...cur,
|
|
...(typeof patch.totalRows === 'number' ? { totalRows: patch.totalRows } : {}),
|
|
...(typeof patch.validRows === 'number' ? { validRows: patch.validRows } : {})
|
|
};
|
|
writeStore({ v: 1, items: [...store.items] }, false);
|
|
}
|