Files
TREK/client/src/components/Files/useFileTrash.ts
T
Maurice 076ed897e7 refactor(files): share the file trash, paste and link rules between desktop and phone
useFileTrash holds the trashed files with their restore, permanent delete
and empty actions; the desktop trash view loads it on demand and the phone
sheet while it is open. fileActions holds the clipboard file picking and the
two tier link toggle (own column first, then link records) that the desktop
assign dialog and the phone link sheet both ran on their own.
2026-10-09 15:19:43 +02:00

93 lines
2.7 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
import { filesApi } from '../../api/client';
import type { useTranslation } from '../../i18n';
import type { TripFile } from '../../types';
import type { useToast } from '../shared/Toast';
export interface FileTrashOptions {
tripId: number;
t: ReturnType<typeof useTranslation>['t'];
toast: ReturnType<typeof useToast>;
/** Reloads the trip's live files after a restore brought one back. */
onRestored: () => void;
/**
* The phone's sheet: the trash loads every time this turns true, and a load still in
* flight when it closes or the trip changes is dropped. Left out, nothing loads by
* itself and the desktop view calls `load` when it opens the trash.
*/
open?: boolean;
}
/**
* The trashed files of a trip behind both shells: the desktop file manager's trash view
* and the phone's trash sheet. Each shell keeps its own confirm step and busy markers and
* calls the actions here once the user said yes.
*/
export function useFileTrash({ tripId, t, toast, onRestored, open }: FileTrashOptions) {
const [files, setFiles] = useState<TripFile[]>([]);
const [loading, setLoading] = useState(false);
const load = useCallback(async () => {
setLoading(true);
try {
const data = await filesApi.list(tripId, true);
setFiles(data.files || []);
} catch {
// An unreachable trash shows as empty.
}
setLoading(false);
}, [tripId]);
useEffect(() => {
if (!open) return;
let cancelled = false;
setLoading(true);
filesApi
.list(tripId, true)
.then((data: { files?: TripFile[] }) => {
if (!cancelled) setFiles(data.files || []);
})
.catch(() => {})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, [open, tripId]);
const restore = async (fileId: number) => {
try {
await filesApi.restore(tripId, fileId);
setFiles((prev) => prev.filter((f) => f.id !== fileId));
onRestored();
toast.success(t('files.toast.restored'));
} catch {
toast.error(t('files.toast.restoreError'));
}
};
const permanentDelete = async (fileId: number) => {
try {
await filesApi.permanentDelete(tripId, fileId);
setFiles((prev) => prev.filter((f) => f.id !== fileId));
toast.success(t('files.toast.deleted'));
} catch {
toast.error(t('files.toast.deleteError'));
}
};
const emptyTrash = async () => {
try {
await filesApi.emptyTrash(tripId);
setFiles([]);
toast.success(t('files.toast.trashEmptied'));
} catch {
toast.error(t('files.toast.deleteError'));
}
};
return { files, loading, load, restore, permanentDelete, emptyTrash };
}