import { useCallback, useMemo, useRef, useState } from "react";
|
import { useListContext, useStore } from "react-admin";
|
import request from "@/utils/request";
|
import {
|
DEFAULT_PRINT_PREVIEW_FETCH_PAGE_SIZE,
|
downloadBlobFile,
|
resolveReportMeta,
|
resolveVisibleColumns,
|
} from "./listReportUtils";
|
|
const getPreferencePath = (preferenceKey, field) =>
|
`preferences.${preferenceKey || "__listReport__"}.${field}`;
|
|
export const useListReportActionParams = (reportConfig, options = {}) => {
|
const { ids: explicitIds } = options;
|
const { filter, filterValues, resource, selectedIds, sort, total } = useListContext();
|
const [storedColumns] = useStore(
|
getPreferencePath(reportConfig?.preferenceKey, "columns"),
|
[]
|
);
|
const [availableColumns] = useStore(
|
getPreferencePath(reportConfig?.preferenceKey, "availableColumns"),
|
[]
|
);
|
const [storedOmit] = useStore(
|
getPreferencePath(reportConfig?.preferenceKey, "omit"),
|
[]
|
);
|
|
const visibleColumns = useMemo(
|
() =>
|
resolveVisibleColumns({
|
columnDefs: reportConfig?.columns,
|
storedColumns,
|
availableColumns,
|
storedOmit,
|
omit: reportConfig?.omit,
|
}),
|
[availableColumns, reportConfig?.columns, reportConfig?.omit, storedColumns, storedOmit]
|
);
|
|
return {
|
visibleColumns,
|
params: {
|
filter,
|
filterValues,
|
ids: explicitIds ?? selectedIds,
|
resource: reportConfig?.resource || resource,
|
sort,
|
total,
|
},
|
};
|
};
|
|
export const useListReportOutput = ({
|
reportConfig,
|
buildRequestPayload,
|
notify,
|
}) => {
|
const [exportLoading, setExportLoading] = useState(false);
|
const [previewOpen, setPreviewOpen] = useState(false);
|
const [previewRows, setPreviewRows] = useState([]);
|
const [previewColumns, setPreviewColumns] = useState([]);
|
const [previewMeta, setPreviewMeta] = useState(null);
|
const [previewDataset, setPreviewDataset] = useState({
|
loadedPages: 0,
|
transportPages: 0,
|
pageSize: reportConfig?.previewPageSize || DEFAULT_PRINT_PREVIEW_FETCH_PAGE_SIZE,
|
total: 0,
|
fullyLoaded: false,
|
});
|
const [previewLoading, setPreviewLoading] = useState(false);
|
const [previewPrefetching, setPreviewPrefetching] = useState(false);
|
const requestIdRef = useRef(0);
|
const cacheRef = useRef(new Map());
|
|
const previewPageSize = reportConfig?.previewPageSize || DEFAULT_PRINT_PREVIEW_FETCH_PAGE_SIZE;
|
|
const buildPayload = useCallback(
|
params => {
|
const payload = typeof buildRequestPayload === "function"
|
? buildRequestPayload(params)
|
: {};
|
if (payload.reportMeta) {
|
return payload;
|
}
|
return {
|
...payload,
|
reportMeta: resolveReportMeta(reportConfig, params),
|
};
|
},
|
[buildRequestPayload, reportConfig]
|
);
|
|
const resetPreviewCache = useCallback(() => {
|
cacheRef.current = new Map();
|
setPreviewRows([]);
|
setPreviewColumns([]);
|
setPreviewMeta(null);
|
setPreviewDataset({
|
loadedPages: 0,
|
transportPages: 0,
|
pageSize: previewPageSize,
|
total: 0,
|
fullyLoaded: false,
|
});
|
}, [previewPageSize]);
|
|
const fetchPreviewPage = useCallback(
|
async ({ payload, current }) => {
|
const { data: { code, data, msg } } = await request.post(
|
`/${reportConfig.resource}/print/query`,
|
{
|
...payload,
|
current,
|
pageSize: previewPageSize,
|
}
|
);
|
|
if (code !== 200) {
|
throw new Error(msg || "Print preview query failed");
|
}
|
|
return data || {};
|
},
|
[previewPageSize, reportConfig.resource]
|
);
|
|
const syncPreviewCache = useCallback(
|
({ totalPages, totalRows }) => {
|
const nextRows = [];
|
for (let pageIndex = 1; pageIndex <= totalPages; pageIndex += 1) {
|
const pageRows = cacheRef.current.get(pageIndex);
|
if (Array.isArray(pageRows)) {
|
nextRows.push(...pageRows);
|
}
|
}
|
|
setPreviewRows(nextRows);
|
setPreviewDataset({
|
loadedPages: cacheRef.current.size,
|
transportPages: totalPages,
|
pageSize: previewPageSize,
|
total: totalRows,
|
fullyLoaded: cacheRef.current.size >= totalPages,
|
});
|
},
|
[previewPageSize]
|
);
|
|
const applyPreviewPage = useCallback(
|
({ data, payload, openPreview = false }) => {
|
const current = Number(data.current) || 1;
|
const totalPages = Number(data.pages) || 0;
|
const totalRows = Number(data.total) || 0;
|
|
setPreviewColumns(payload.columns || []);
|
setPreviewMeta(payload.reportMeta || null);
|
cacheRef.current.set(current, Array.isArray(data.records) ? data.records : []);
|
syncPreviewCache({ totalPages, totalRows });
|
|
if (openPreview) {
|
setPreviewOpen(true);
|
}
|
},
|
[syncPreviewCache]
|
);
|
|
const prefetchPreviewPages = useCallback(
|
async ({ payload, totalPages, totalRows, requestId }) => {
|
if (totalPages <= 1) {
|
setPreviewPrefetching(false);
|
return;
|
}
|
|
setPreviewPrefetching(true);
|
const pendingPages = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
|
const workerCount = Math.min(3, pendingPages.length);
|
let hasErrored = false;
|
|
const worker = async () => {
|
while (pendingPages.length > 0 && requestIdRef.current === requestId) {
|
const nextPage = pendingPages.shift();
|
if (!nextPage) {
|
return;
|
}
|
|
try {
|
const data = await fetchPreviewPage({ payload, current: nextPage });
|
if (requestIdRef.current !== requestId) {
|
return;
|
}
|
cacheRef.current.set(nextPage, Array.isArray(data.records) ? data.records : []);
|
syncPreviewCache({ totalPages, totalRows });
|
} catch (error) {
|
if (!hasErrored && requestIdRef.current === requestId) {
|
hasErrored = true;
|
console.error(error);
|
notify(error.message || "ra.notification.http_error", { type: "error" });
|
}
|
return;
|
}
|
}
|
};
|
|
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
if (requestIdRef.current === requestId) {
|
setPreviewPrefetching(false);
|
}
|
},
|
[fetchPreviewPage, notify, syncPreviewCache]
|
);
|
|
const openPrintPreview = useCallback(
|
async params => {
|
if (!reportConfig?.enablePrint) {
|
return;
|
}
|
setPreviewLoading(true);
|
try {
|
requestIdRef.current += 1;
|
const requestId = requestIdRef.current;
|
resetPreviewCache();
|
const payload = buildPayload(params);
|
const data = await fetchPreviewPage({ payload, current: 1 });
|
applyPreviewPage({ data, payload, openPreview: true });
|
prefetchPreviewPages({
|
payload,
|
totalPages: Number(data.pages) || 0,
|
totalRows: Number(data.total) || 0,
|
requestId,
|
});
|
} catch (error) {
|
console.error(error);
|
notify(error.message || "ra.notification.http_error", { type: "error" });
|
} finally {
|
setPreviewLoading(false);
|
}
|
},
|
[
|
applyPreviewPage,
|
buildPayload,
|
fetchPreviewPage,
|
notify,
|
prefetchPreviewPages,
|
reportConfig?.enablePrint,
|
resetPreviewCache,
|
]
|
);
|
|
const closePrintPreview = useCallback(() => {
|
requestIdRef.current += 1;
|
setPreviewPrefetching(false);
|
setPreviewOpen(false);
|
}, []);
|
|
const exportReport = useCallback(
|
async params => {
|
setExportLoading(true);
|
try {
|
const payload = buildPayload(params);
|
const response = await request.post(`/${reportConfig.resource}/export`, payload, {
|
responseType: "blob",
|
});
|
downloadBlobFile(
|
response,
|
reportConfig.exportFileName || `${reportConfig.resource}.xlsx`
|
);
|
} catch (error) {
|
console.error(error);
|
notify("ra.notification.http_error", { type: "error" });
|
} finally {
|
setExportLoading(false);
|
}
|
},
|
[buildPayload, notify, reportConfig.exportFileName, reportConfig.resource]
|
);
|
|
return {
|
exportLoading,
|
previewOpen,
|
previewRows,
|
previewColumns,
|
previewMeta,
|
previewDataset,
|
previewLoading,
|
previewPrefetching,
|
openPrintPreview,
|
closePrintPreview,
|
exportReport,
|
};
|
};
|