mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-08-17 21:25:54 +02:00
364 lines
12 KiB
React
364 lines
12 KiB
React
import { useEffect, useState, useRef } from "react";
|
|
import Parse from "parse";
|
|
import axios from "axios";
|
|
import reportJson, { extraCols } from "../json/ReportJson";
|
|
import { useParams } from "react-router";
|
|
import PageNotFound from "./PageNotFound";
|
|
import Loader from "../primitives/Loader";
|
|
import Contactbook from "../reports/contact/Contactbook";
|
|
import ColumnSelector from "../components/ColumnSelector";
|
|
import TemplatesReport from "../reports/template/TemplatesReport";
|
|
import DocumentsReport from "../reports/document/DocumentsReport";
|
|
import { templateReportTour } from "../json/ReportTour";
|
|
import { withSessionValidation } from "../utils";
|
|
|
|
const Report = () => {
|
|
const { id } = useParams();
|
|
const abortController = new AbortController();
|
|
const [list, setList] = useState([]);
|
|
const [isLoader, setIsLoader] = useState(true);
|
|
const [reportName, setReportName] = useState("");
|
|
const [reporthelp, setReportHelp] = useState("");
|
|
const [actions, setActions] = useState([]);
|
|
const [heading, setHeading] = useState([]);
|
|
const [isNextRecord, setIsNextRecord] = useState(false);
|
|
const [isMoreDocs, setIsMoreDocs] = useState(true);
|
|
const [tourData, setTourData] = useState([]);
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
const [signerStatusFilter, setSignerStatusFilter] = useState("all");
|
|
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
|
const [isSearchResult, setIsSearchResult] = useState(false);
|
|
const [allColumns, setAllColumns] = useState([]);
|
|
const [visibleColumns, setVisibleColumns] = useState([]);
|
|
const [columnLabels, setColumnLabels] = useState({});
|
|
const [defaultColumns, setDefaultColumns] = useState([]);
|
|
const [isColumnModal, setIsColumnModal] = useState(false);
|
|
const [searchLoader, setSearchLoader] = useState(false);
|
|
const debounceTimer = useRef(null);
|
|
const hasMountedSignerFilter = useRef(false);
|
|
const searchAbortRef = useRef(null); // holds AbortController
|
|
// Number of documents to display per page (should always be half of docLimit for proper pagination)
|
|
const docPerPage = 10;
|
|
// Number of documents to fetch per API call
|
|
const docLimit = 20;
|
|
|
|
|
|
// below useEffect is call when id param change
|
|
useEffect(() => {
|
|
setReportName("");
|
|
setList([]);
|
|
setSearchTerm("");
|
|
setSignerStatusFilter("all");
|
|
setMobileSearchOpen(false);
|
|
const saved = JSON.parse(localStorage.getItem("reportColumns") || "{}");
|
|
if (saved[id]) {
|
|
setVisibleColumns(saved[id].visible || saved[id]);
|
|
setColumnLabels(saved[id].labels || {});
|
|
} else {
|
|
setVisibleColumns([]);
|
|
setColumnLabels({});
|
|
}
|
|
getReportData(0, docLimit, "");
|
|
|
|
// Function returned from useEffect is called on unmount
|
|
return () => {
|
|
setIsLoader(true);
|
|
setList([]);
|
|
setIsNextRecord(false);
|
|
// Here it'll abort the fetch
|
|
abortController.abort();
|
|
if (searchAbortRef.current) {
|
|
searchAbortRef.current.abort();
|
|
searchAbortRef.current = null;
|
|
}
|
|
};
|
|
// eslint-disable-next-line
|
|
}, [id]);
|
|
|
|
// below useEffect call when isNextRecord state is true and fetch next record
|
|
useEffect(() => {
|
|
if (isNextRecord) {
|
|
getReportData(list?.length, docLimit, searchTerm);
|
|
}
|
|
// eslint-disable-next-line
|
|
}, [isNextRecord]);
|
|
|
|
useEffect(() => {
|
|
if (!hasMountedSignerFilter.current) {
|
|
hasMountedSignerFilter.current = true;
|
|
return;
|
|
}
|
|
setList([]);
|
|
setIsNextRecord(false);
|
|
setIsMoreDocs(true);
|
|
getDocumentbyUserStatus();
|
|
// eslint-disable-next-line
|
|
}, [signerStatusFilter]);
|
|
|
|
const getDocumentbyUserStatus = async () => {
|
|
setSearchLoader(true);
|
|
try {
|
|
await getReportData(0, docLimit, searchTerm, signerStatusFilter);
|
|
} catch (error) {
|
|
console.error("get report by user status error", error);
|
|
} finally {
|
|
setSearchLoader(false);
|
|
}
|
|
};
|
|
|
|
const handleSearchChange = withSessionValidation(async (e) => {
|
|
const term = e.target.value.toLowerCase();
|
|
setSearchTerm(term);
|
|
if (debounceTimer.current) {
|
|
clearTimeout(debounceTimer.current);
|
|
}
|
|
// 2) abort any in-flight request triggered by older debounce
|
|
if (searchAbortRef.current) {
|
|
searchAbortRef.current.abort();
|
|
searchAbortRef.current = null;
|
|
}
|
|
debounceTimer.current = setTimeout(async () => {
|
|
// create controller for THIS request
|
|
const controller = new AbortController();
|
|
searchAbortRef.current = controller;
|
|
|
|
setSearchLoader(true);
|
|
try {
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
|
sessiontoken: localStorage.getItem("accesstoken")
|
|
};
|
|
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
|
const res = await axios.post(
|
|
url,
|
|
{
|
|
reportId: id,
|
|
searchTerm: term,
|
|
signerStatus: signerStatusFilter,
|
|
skip: 0,
|
|
limit: docPerPage
|
|
},
|
|
{ headers: headers, signal: controller.signal } // ✅ axios abort
|
|
);
|
|
// if you want to be extra safe (ignore late responses)
|
|
if (controller.signal.aborted) return;
|
|
|
|
const data = res.data?.result || [];
|
|
if (!data.error) {
|
|
setList(data);
|
|
setIsMoreDocs(data.length >= docPerPage);
|
|
setIsNextRecord(false);
|
|
setIsSearchResult(true);
|
|
setSearchLoader(false);
|
|
}
|
|
} catch (err) {
|
|
// ✅ ignore abort errors
|
|
const isAbort =
|
|
err?.name === "CanceledError" ||
|
|
err?.code === "ERR_CANCELED" ||
|
|
err?.message?.toLowerCase?.().includes("canceled");
|
|
if (!isAbort) console.error("Search error:", err);
|
|
setSearchLoader(false);
|
|
}
|
|
}, 300);
|
|
setIsSearchResult(false);
|
|
});
|
|
|
|
const handleSearchPaste = (e) => {
|
|
setTimeout(() => {
|
|
handleSearchChange({ target: { value: e.target.value } });
|
|
}, 0);
|
|
};
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
if (debounceTimer.current) {
|
|
clearTimeout(debounceTimer.current);
|
|
}
|
|
};
|
|
}, []);
|
|
const getReportData = withSessionValidation(
|
|
async (
|
|
skipUserRecord = 0,
|
|
limit = 20,
|
|
term = searchTerm,
|
|
signerStatus = signerStatusFilter
|
|
) => {
|
|
// setIsLoader(true);
|
|
const json = reportJson(id);
|
|
if (json) {
|
|
setActions(json.actions);
|
|
const savedCols = JSON.parse(
|
|
localStorage.getItem("reportColumns") || "{}"
|
|
);
|
|
const visible = savedCols[id]?.visible || json.heading;
|
|
const labels = savedCols[id]?.labels || {};
|
|
if (!savedCols[id] || id === "contacts") {
|
|
savedCols[id] = { visible: json.heading, labels: {} };
|
|
localStorage.setItem("reportColumns", JSON.stringify(savedCols));
|
|
}
|
|
setVisibleColumns(visible);
|
|
setColumnLabels(labels);
|
|
setHeading(visible);
|
|
setDefaultColumns(json.heading);
|
|
setReportName(json.reportName);
|
|
setReportHelp(json?.helpMsg);
|
|
const currentUser = Parse.User.current().id;
|
|
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
|
sessiontoken: localStorage.getItem("accesstoken")
|
|
};
|
|
try {
|
|
const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord;
|
|
const limitRecord = id === "4Hhwbp482K" ? 200 : limit;
|
|
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
|
if (term) {
|
|
params.searchTerm = term;
|
|
}
|
|
if (signerStatus && signerStatus !== "all") {
|
|
params.signerStatus = signerStatus;
|
|
}
|
|
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
|
const res = await axios.post(url, params, {
|
|
headers: headers,
|
|
signal: abortController.signal // is used to cancel fetch query
|
|
});
|
|
const extraHeads =
|
|
id === "4Hhwbp482K" ? [...extraCols, "Expiry Date"] : extraCols;
|
|
setAllColumns(Array.from(new Set([...json.heading, ...extraHeads])));
|
|
if (id === "6TeaPr321t") {
|
|
setTourData(templateReportTour);
|
|
}
|
|
if (id === "4Hhwbp482K") {
|
|
const listData = res.data?.result.filter(
|
|
(x) => x.Signers.length > 0
|
|
);
|
|
let arr = [];
|
|
for (const obj of listData) {
|
|
const isSigner = obj?.Signers?.some(
|
|
(item) => item.UserId.objectId === currentUser
|
|
);
|
|
if (isSigner) {
|
|
let isRecord;
|
|
if (obj?.AuditTrail && obj?.AuditTrail.length > 0) {
|
|
isRecord = obj?.AuditTrail.some(
|
|
(item) =>
|
|
item?.UserPtr?.UserId?.objectId === currentUser &&
|
|
item.Activity === "Signed"
|
|
);
|
|
} else {
|
|
isRecord = false;
|
|
}
|
|
if (isRecord === false) {
|
|
arr.push(obj);
|
|
}
|
|
}
|
|
}
|
|
if (arr.length === docPerPage) {
|
|
setIsMoreDocs(true);
|
|
} else {
|
|
setIsMoreDocs(false);
|
|
}
|
|
setList((prevRecord) =>
|
|
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
|
);
|
|
} else {
|
|
if (res.data.result.length >= docPerPage) {
|
|
setIsMoreDocs(true);
|
|
} else {
|
|
setIsMoreDocs(false);
|
|
}
|
|
if (!res.data.result.error) {
|
|
setIsNextRecord(false);
|
|
setList((prevRecord) =>
|
|
prevRecord.length > 0
|
|
? [...prevRecord, ...res.data.result]
|
|
: res.data.result
|
|
);
|
|
}
|
|
}
|
|
setIsLoader(false);
|
|
} catch (err) {
|
|
const isCancel = axios.isCancel(err);
|
|
if (!isCancel) {
|
|
console.error("getreport error", err);
|
|
setIsLoader(false);
|
|
}
|
|
}
|
|
} else {
|
|
setIsLoader(false);
|
|
}
|
|
}
|
|
);
|
|
|
|
const commonProps = {
|
|
ReportName: reportName,
|
|
List: list,
|
|
setList,
|
|
actions: actions,
|
|
heading: heading,
|
|
setIsNextRecord,
|
|
isMoreDocs,
|
|
docPerPage,
|
|
mobileSearchOpen,
|
|
setMobileSearchOpen,
|
|
searchTerm,
|
|
handleSearchChange,
|
|
handleSearchPaste,
|
|
isSearchResult,
|
|
columnLabels,
|
|
searchLoader,
|
|
signerStatusFilter,
|
|
handleSignerStatusFilter: setSignerStatusFilter,
|
|
openColumnModal: () => setIsColumnModal(true)
|
|
};
|
|
return (
|
|
<>
|
|
{isLoader ? (
|
|
<div className="h-[100vh] flex justify-center items-center">
|
|
<Loader />
|
|
</div>
|
|
) : (
|
|
<>
|
|
{id === "contacts" ? (
|
|
<Contactbook {...commonProps} />
|
|
) : id === "6TeaPr321t" ? (
|
|
<TemplatesReport
|
|
{...commonProps}
|
|
report_help={reporthelp}
|
|
tourData={tourData}
|
|
/>
|
|
) : reportName ? (
|
|
<DocumentsReport {...commonProps} report_help={reporthelp} />
|
|
) : (
|
|
<PageNotFound prefix={"Report"} />
|
|
)}
|
|
</>
|
|
)}
|
|
<ColumnSelector
|
|
isOpen={isColumnModal}
|
|
allColumns={allColumns}
|
|
visibleColumns={visibleColumns}
|
|
columnLabels={columnLabels}
|
|
defaultColumns={defaultColumns}
|
|
onApply={(cols, labels) => {
|
|
setVisibleColumns(cols);
|
|
setHeading(cols);
|
|
setColumnLabels(labels);
|
|
const saved = JSON.parse(
|
|
localStorage.getItem("reportColumns") || "{}"
|
|
);
|
|
saved[id] = { visible: cols, labels };
|
|
localStorage.setItem("reportColumns", JSON.stringify(saved));
|
|
}}
|
|
onClose={() => setIsColumnModal(false)}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Report;
|