mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
64ceecefb3
Every booking and stay write went straight to the API, so offline a new
booking failed with "Network Error" and was lost, and a status tick, the
travellers or a moved transport snapped back. Bookings (add, edit, status,
travellers, delete, day order) and stays (add, edit, delete) now go through
reservationRepo and accommodationRepo and the mutation queue like places and
costs: a temporary row offline, held behind an older queued write online,
since neither carries a version token. Desktop, the phone sheets, the day
plan, the road trip nights and the PDF all write and read through them.
The replay reads the booking from `reservation` only, so the travellers
answer ({ travelers, reservation }) lands on the booking it names. A booking
or its stay that names a place or stay created offline is sent with the real
id once that one has synced, inside create_accommodation too, so an imported
hotel reviewed offline can be linked to a place made for it.
What only works online says so instead of failing: an AirTrail import, a
booking file import, the AI retry and an attachment on a saved booking show
that they need a connection, and a save with newly picked files is stopped
with a hint, since an upload cannot wait in the queue.
Refs #2601
79 lines
2.9 KiB
TypeScript
79 lines
2.9 KiB
TypeScript
import type React from 'react';
|
|
import { type Dispatch, type SetStateAction, useState } from 'react';
|
|
|
|
import { isEffectivelyOffline } from '../../sync/networkMode';
|
|
import type { TripFile } from '../../types';
|
|
import { attachedBookingFiles } from './bookingFormModel';
|
|
|
|
export interface BookingFileAttachOptions {
|
|
/** The booking being edited; files picked before it is saved wait in `pendingFiles`. */
|
|
reservation: { id: number; title: string } | null | undefined;
|
|
/** The trip's files. */
|
|
files: TripFile[];
|
|
onFileUpload?: (fd: FormData) => Promise<unknown>;
|
|
setPendingFiles: Dispatch<SetStateAction<File[]>>;
|
|
/** The toast and translate the dialog already holds. */
|
|
toast: { success: (message: string) => void; error: (message: string) => void };
|
|
t: (key: string) => string;
|
|
}
|
|
|
|
/**
|
|
* The Files row of the desktop booking and transport dialogs: a file picked on a
|
|
* saved booking goes up at once, one picked on a new booking waits for the save,
|
|
* and the files linked or detached in the dialog show on the booking right away.
|
|
*/
|
|
export function useBookingFileAttach({
|
|
reservation,
|
|
files,
|
|
onFileUpload,
|
|
setPendingFiles,
|
|
toast,
|
|
t,
|
|
}: BookingFileAttachOptions) {
|
|
const [uploadingFile, setUploadingFile] = useState(false);
|
|
const [linkedFileIds, setLinkedFileIds] = useState<number[]>([]);
|
|
|
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (!file) return;
|
|
if (reservation?.id && isEffectivelyOffline()) {
|
|
// A file goes up at once on a saved booking, and an upload cannot wait in
|
|
// the offline queue.
|
|
toast.error(t('reservations.needsConnection'));
|
|
e.target.value = '';
|
|
} else if (reservation?.id) {
|
|
setUploadingFile(true);
|
|
try {
|
|
if (onFileUpload) {
|
|
const fd = new FormData();
|
|
fd.append('file', file);
|
|
fd.append('reservation_id', String(reservation.id));
|
|
fd.append('description', reservation.title);
|
|
await onFileUpload(fd);
|
|
toast.success(t('reservations.toast.fileUploaded'));
|
|
} else {
|
|
// A dialog without an upload reports a failed upload, as it always did.
|
|
toast.error(t('reservations.toast.uploadError'));
|
|
}
|
|
} catch {
|
|
toast.error(t('reservations.toast.uploadError'));
|
|
} finally {
|
|
setUploadingFile(false);
|
|
e.target.value = '';
|
|
}
|
|
} else {
|
|
setPendingFiles((prev) => [...prev, file]);
|
|
e.target.value = '';
|
|
}
|
|
};
|
|
|
|
return {
|
|
uploadingFile,
|
|
handleFileChange,
|
|
attachedFiles: attachedBookingFiles(files, reservation?.id, linkedFileIds),
|
|
removePending: (index: number) => setPendingFiles((prev) => prev.filter((_, j) => j !== index)),
|
|
linkFile: (fileId: number) => setLinkedFileIds((prev) => [...prev, fileId]),
|
|
detachFile: (fileId: number) => setLinkedFileIds((prev) => prev.filter((id) => id !== fileId)),
|
|
};
|
|
}
|