Files
TREK/client/src/components/Packing/usePackingImport.ts
T
Maurice fee41ab394 fix(collab): a note, poll, message or link that cannot be saved offline says it needs a connection
Collab, packing bags, packing templates and imports, the category a place
form creates and the place imports are not kept on the device, so offline
their writes cannot wait in the queue. They failed with a bare "Error" or the
generic save message. While TREK is offline the failure now reads "This needs
a connection. Try again once TREK is back online.", on desktop and on the
phone's Collab notes; online nothing changes.

Refs #2601
2026-10-12 20:18:04 +02:00

78 lines
2.4 KiB
TypeScript

import { useMemo, useState, type ChangeEvent } from 'react';
import { packingApi } from '../../api/client';
import { useTripStore } from '../../store/tripStore';
import { offlineOr } from '../../utils/needsConnection';
import { parseImportLines } from './packingListPanel.helpers';
type Translate = (key: string, params?: Record<string, string | number>) => string;
interface Toaster {
success: (message: string) => void;
error: (message: string) => void;
}
/**
* The bulk packing import behind the desktop dialog and the phone sheet: the
* pasted or loaded text, one item per line, and sending it in one request whose
* items land straight in the trip store. `onImported` closes the surface.
*
* The desktop dialog says so when no line parses (`reportEmpty`); the phone sheet
* keeps its button disabled then, and lets one import run at a time (`oneAtATime`).
*/
export function usePackingImport({
tripId,
t,
toast,
onImported,
reportEmpty = false,
oneAtATime = false,
}: {
tripId: number;
t: Translate;
toast: Toaster;
onImported: () => void;
reportEmpty?: boolean;
oneAtATime?: boolean;
}) {
const [text, setText] = useState('');
const [importing, setImporting] = useState(false);
const parsed = useMemo(() => parseImportLines(text), [text]);
/** Loads a picked CSV, text or Markdown file into the text box. */
const readFile = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
e.target.value = '';
const reader = new FileReader();
reader.onload = () => {
if (typeof reader.result === 'string') setText(reader.result);
};
reader.readAsText(file);
};
const runImport = async () => {
const lines = parseImportLines(text);
if (lines.length === 0) {
if (reportEmpty) toast.error(t('packing.importEmpty'));
return;
}
if (oneAtATime) {
if (importing) return;
setImporting(true);
}
try {
const result = await packingApi.bulkImport(tripId, lines);
useTripStore.setState((s) => ({ packingItems: [...s.packingItems, ...(result.items || [])] }));
toast.success(t('packing.importSuccess', { count: result.count }));
setText('');
onImported();
} catch {
toast.error(offlineOr(t, t('packing.importError')));
} finally {
if (oneAtATime) setImporting(false);
}
};
return { text, setText, parsed, importing, readFile, runImport };
}