This commit is contained in:
Andras Schmelczer 2026-05-26 19:45:13 +01:00
parent c645b0f1d4
commit 39ef5c6646
79 changed files with 5660 additions and 2199 deletions

View file

@ -135,73 +135,86 @@ export function useExportController({
useEffect(() => clearExportNoticeTimer, [clearExportNoticeTimer]);
const handleExport = useCallback(() => {
if (exporting) return;
if (!bounds) {
showExportNotice({ kind: 'error', message: t('header.exportUnavailable') });
return;
}
const handleExport = useCallback(
(options?: { postcodes?: string[] }) => {
if (exporting) return;
const { south, west, north, east } = bounds;
const params = new URLSearchParams({
bounds: `${south},${west},${north},${east}`,
});
const filterStr = buildFilterString(filters, features);
if (filterStr) params.set('filters', filterStr);
const travelParam = buildTravelParam(travelTimeEntries);
if (travelParam) params.set('travel', travelParam);
appendTravelStateParams(params, travelTimeEntries);
for (const overlay of selectedOverlays) {
params.append('overlay', overlay);
}
if (shareCode) params.set('share', shareCode);
const url = apiUrl('export', params);
const postcodeList = options?.postcodes?.map((p) => p.trim()).filter(Boolean) ?? [];
const isListMode = postcodeList.length > 0;
const controller = new AbortController();
let timedOut = false;
const timeoutId = window.setTimeout(() => {
timedOut = true;
controller.abort();
}, EXPORT_TIMEOUT_MS);
setExporting(true);
clearExportNotice();
void (async () => {
try {
const res = await fetch(url, authHeaders({ signal: controller.signal }));
if (!res.ok) throw new Error(await getExportErrorMessage(res));
const blob = await res.blob();
if (blob.size === 0) throw new Error(t('header.exportEmpty'));
triggerExportDownload(blob, getExportFileName(res));
trackEvent('Export');
showExportNotice({ kind: 'success', message: t('header.exportReady') });
} catch (err) {
if (!timedOut) logNonAbortError('Export failed', err);
const detail = err instanceof Error && err.message.trim() ? ` ${err.message}` : '';
showExportNotice({
kind: 'error',
message: timedOut ? t('header.exportTimedOut') : `${t('header.exportFailed')}${detail}`,
});
} finally {
window.clearTimeout(timeoutId);
setExporting(false);
if (!isListMode && !bounds) {
showExportNotice({ kind: 'error', message: t('header.exportUnavailable') });
return;
}
})();
}, [
bounds,
clearExportNotice,
exporting,
features,
filters,
selectedOverlays,
shareCode,
showExportNotice,
t,
travelTimeEntries,
]);
const params = new URLSearchParams();
if (isListMode) {
params.set('postcodes', postcodeList.join(','));
if (shareCode) params.set('share', shareCode);
} else {
const { south, west, north, east } = bounds!;
params.set('bounds', `${south},${west},${north},${east}`);
const filterStr = buildFilterString(filters, features);
if (filterStr) params.set('filters', filterStr);
const travelParam = buildTravelParam(travelTimeEntries);
if (travelParam) params.set('travel', travelParam);
appendTravelStateParams(params, travelTimeEntries);
for (const overlay of selectedOverlays) {
params.append('overlay', overlay);
}
if (shareCode) params.set('share', shareCode);
}
const url = apiUrl('export', params);
const controller = new AbortController();
let timedOut = false;
const timeoutId = window.setTimeout(() => {
timedOut = true;
controller.abort();
}, EXPORT_TIMEOUT_MS);
setExporting(true);
clearExportNotice();
void (async () => {
try {
const res = await fetch(url, authHeaders({ signal: controller.signal }));
if (!res.ok) throw new Error(await getExportErrorMessage(res));
const blob = await res.blob();
if (blob.size === 0) throw new Error(t('header.exportEmpty'));
triggerExportDownload(blob, getExportFileName(res));
trackEvent('Export');
showExportNotice({ kind: 'success', message: t('header.exportReady') });
} catch (err) {
if (!timedOut) logNonAbortError('Export failed', err);
const detail = err instanceof Error && err.message.trim() ? ` ${err.message}` : '';
showExportNotice({
kind: 'error',
message: timedOut
? t('header.exportTimedOut')
: `${t('header.exportFailed')}${detail}`,
});
} finally {
window.clearTimeout(timeoutId);
setExporting(false);
}
})();
},
[
bounds,
clearExportNotice,
exporting,
features,
filters,
selectedOverlays,
shareCode,
showExportNotice,
t,
travelTimeEntries,
]
);
useEffect(() => {
onExportStateChange?.({ onExport: handleExport, exporting });