import React, { useState, useRef, useEffect } from "react";
import {
collection,
query,
where,
getDocs,
startAfter,
limit,
startAt,
} from "firebase/firestore";
import { db } from "../firebase";
import * as XLSX from "xlsx";
import { useReactToPrint } from "react-to-print";
import { useSelector } from "react-redux";
const FilterProducts = () => {
const [filteredProducts, setFilteredProducts] = useState([]);
const processesList = useSelector((state) => state.processes.value);
const [lastVisible, setLastVisible] = useState(null);
const [hasMoreProducts, setHasMoreProducts] = useState(true);
const [selectedProcess, setSelectedProcess] = useState("");
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [sortCriteria, setSortCriteria] = useState("");
const [sortDirection, setSortDirection] = useState("asc");
const tableRef = useRef();
const loaderRef = useRef(null);
useEffect(() => {
if (filteredProducts.length > 0) {
const sortedProducts = sortProducts(filteredProducts);
setFilteredProducts([...sortedProducts]);
}
}, [sortCriteria, sortDirection]);
const buildQueryWithFilters = (baseQuery) => {
let queryDescription = "Query Filters: \n";
if (startDate && endDate) {
baseQuery = query(
baseQuery,
where("exFactoryDate", ">=", startDate),
where("exFactoryDate", "<=", endDate)
);
queryDescription += ` - ExFactoryDate between ${startDate} and ${endDate}\n`;
}
if (selectedProcess) {
baseQuery = query(
baseQuery,
where(`processes.${selectedProcess}`, "==", {})
);
queryDescription += ` - Process: ${selectedProcess}\n`;
}
console.log(queryDescription);
return baseQuery;
};
const fetchFilteredProducts = async () => {
if (!selectedProcess || !startDate || !endDate) {
setErrorMessage(
"Please select all filters: Process, Start Date, and End Date."
);
return;
}
try {
let productsQuery = query(collection(db, "products"), limit(20));
productsQuery = buildQueryWithFilters(productsQuery);
console.log(
"Products query after filters applied: ",
JSON.stringify(productsQuery)
);
const productsSnapshot = await getDocs(productsQuery);
console.log("Fetched products: ", productsSnapshot);
const productsList = productsSnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setFilteredProducts(productsList);
setLastVisible(productsSnapshot.docs[productsSnapshot.docs.length - 1]);
setHasMoreProducts(productsList.length === 20);
setErrorMessage("");
} catch (error) {
setErrorMessage("Error fetching products. Please try again.");
}
};
const fetchMoreProducts = async () => {
if (!lastVisible || !hasMoreProducts) return;
console.log("Last visible document ID: ", lastVisible.id);
let productsQuery = query(
collection(db, "products"),
startAfter(lastVisible),
limit(20)
);
productsQuery = buildQueryWithFilters(productsQuery);
console.log("Products query after filters applied: ", JSON.stringify(productsQuery));
try {
const productsSnapshot = await getDocs(productsQuery);
const productsList = productsSnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
console.log("Fetched products: ", productsSnapshot);
setFilteredProducts((prevProducts) => [...prevProducts, ...productsList]);
setLastVisible(productsSnapshot.docs[productsSnapshot.docs.length - 1]);
setHasMoreProducts(productsList.length === 20);
} catch (error) {
console.error("Error fetching more products: ", error);
}
};
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasMoreProducts) {
console.log("Loader is visible, fetching more products...");
console.log(lastVisible);
fetchMoreProducts();
}
},
{ threshold: 1 }
);
if (loaderRef.current) {
observer.observe(loaderRef.current);
}
return () => {
if (loaderRef.current) {
observer.unobserve(loaderRef.current);
}
};
}, [loaderRef, lastVisible]);
const sortProducts = (products) => {
return products.sort((a, b) => {
const aValue = a[sortCriteria];
const bValue = b[sortCriteria];
if (aValue < bValue) {
return sortDirection === "asc" ? -1 : 1;
}
if (aValue > bValue) {
return sortDirection === "asc" ? 1 : -1;
}
return 0;
});
};
const handleProcessChange = (e) => {
setSelectedProcess(e.target.value);
};
const handleStartDateChange = (e) => {
setStartDate(e.target.value);
};
const handleEndDateChange = (e) => {
setEndDate(e.target.value);
};
const handleSortChange = (criteria) => {
if (sortCriteria === criteria) {
setSortDirection(sortDirection === "asc" ? "desc" : "asc");
} else {
setSortCriteria(criteria);
setSortDirection("asc");
}
};
const handlePrint = useReactToPrint({ content: () => tableRef.current });
const exportToExcel = () => {
const headers = [
"Sr. No",
"Buyer",
"Buyer PO",
"Exfactory Date",
"Shipment Date",
"Style Name",
"Size",
"Color",
"Quantity",
];
const selectedProcessRow = [`${selectedProcess ? selectedProcess : ""}`];
const data = filteredProducts.map((product) => [
product.srNo,
product.buyer,
product.buyerPO,
product.exFactoryDate,
product.shipmentDate,
product.styleName,
product.size,
product.color,
product.quantity,
]);
const worksheetData = [selectedProcessRow, headers, ...data];
const worksheet = XLSX.utils.aoa_to_sheet(worksheetData);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Products");
XLSX.writeFile(
workbook,
selectedProcess ? selectedProcess + ".xlsx" : "process.xlsx"
);
};
const handleFilterClick = () => {
if (!selectedProcess || !startDate || !endDate) {
setErrorMessage(
"Please select all filters: Process, Start Date, and End Date."
);
} else {
setErrorMessage("");
fetchFilteredProducts(); // Pass true to reset the pagination
}
};
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Planning Sheet</h1>
<div className="mb-4">
<label className="block text-gray-700">Process:</label>
<select
value={selectedProcess}
onChange={handleProcessChange}
className="p-2 border rounded w-full"
>
<option value="">All Processes</option>
{processesList.map((process, index) => (
<option key={index} value={process}>
{process}
</option>
))}
</select>
</div>
<div className="mb-4">
<label className="block text-gray-700">Start Date:</label>
<input
type="date"
value={startDate}
onChange={handleStartDateChange}
className="p-2 border rounded w-full"
/>
</div>
<div className="mb-4">
<label className="block text-gray-700">End Date:</label>
<input
type="date"
value={endDate}
onChange={handleEndDateChange}
className="p-2 border rounded w-full"
/>
</div>
<button
onClick={handleFilterClick}
className="bg-blue-500 text-white px-4 py-2 rounded mb-4 hover:bg-blue-600"
>
Filter
</button>
{errorMessage && <div className="text-red-500 mb-4">{errorMessage}</div>}
{filteredProducts.length > 0 && (
<>
<button
onClick={handlePrint}
className="bg-red-500 text-white px-4 py-2 rounded mb-4 hover:bg-red-600"
>
Print
</button>
<button
onClick={exportToExcel}
className="bg-green-500 text-white px-4 py-2 rounded mb-4 hover:bg-green-600"
>
Export to Excel
</button>
<div className="overflow-x-auto">
<table
ref={tableRef}
className="min-w-full bg-white border border-gray-300"
>
<thead>
<tr>
{[
"srNo",
"buyer",
"buyerPO",
"exFactoryDate",
"shipmentDate",
"styleName",
"size",
"color",
"quantity",
].map((field, index) => (
<th
key={index}
className="py-2 px-4 border-b border-gray-300 cursor-pointer"
onClick={() => handleSortChange(field)}
>
{field.charAt(0).toUpperCase() + field.slice(1)}
{sortCriteria === field && (
<span>{sortDirection === "asc" ? " ▲" : " ▼"}</span>
)}
</th>
))}
</tr>
</thead>
<tbody>
{filteredProducts.map((product) => (
<tr
key={product.id}
className="cursor-pointer hover:bg-gray-100"
>
<td className="py-2 px-4 border-b border-gray-300 text-center">
{product.srNo}
</td>
<td className="py-2 px-4 border-b border-gray-300 text-center">
{product.buyer}
</td>
<td className="text-center py-2 px-4 border-b border-gray-300">
{product.buyerPO}
</td>
<td className="text-center py-2 px-4 border-b border-gray-300">
{product.exFactoryDate}
</td>
<td className=" text-center py-2 px-4 border-b border-gray-300">
{product.shipmentDate}
</td>
<td className="py-2 px-4 text-center border-b border-gray-300">
{product.styleName}
</td>
<td className="py-2 px-4 border-b text-center border-gray-300">
{product.size}
</td>
<td className="py-2 px-4 border-b border-gray-300 text-center">
{product.color}
</td>
<td className="py-2 px-4 border-b border-gray-300 text-center">
{product.quantity}
</td>
</tr>
))}
</tbody>
</table>
</div>
{hasMoreProducts ? (
<div ref={loaderRef} className="py-4 text-center">
<span>Loading more products...</span>
</div>
) : (
<div className="py-4 text-center">
<span>No more products to load</span>
</div>
)}
</>
)}
</div>
);
};
export default FilterProducts;
实际上,在这个文件中的 fetchMoreProducts 函数中
const fetchMoreProducts = async () => {
if (!lastVisible || !hasMoreProducts) return;
console.log("Last visible document ID: ", lastVisible.id);
let productsQuery = query(
collection(db, "products"),
startAfter(lastVisible),
limit(20)
);
productsQuery = buildQueryWithFilters(productsQuery);
console.log("Products query after filters applied: ", JSON.stringify(productsQuery));
try {
const productsSnapshot = await getDocs(productsQuery);
const productsList = productsSnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
console.log("Fetched products: ", productsSnapshot);
setFilteredProducts((prevProducts) => [...prevProducts, ...productsList]);
setLastVisible(productsSnapshot.docs[productsSnapshot.docs.length - 1]);
setHasMoreProducts(productsList.length === 20);
} catch (error) {
console.error("Error fetching more products: ", error);
}
};
它无法从数据库中获取任何内容
现在,我确实尝试从查询中删除 startAfter 并获得输出,但它重新获取了之前的 20 个产品。我希望它只能在先前获取的最后一个产品之后获取产品。我愿意接受提问...
您共享的代码似乎都没有给出
lastVisible
值,因此您将 null
传递给 startAfter
- 这解释了为什么您没有得到结果。
如果
null
有值,则仅向查询传递 startAfter
子句,而不是传递 lastVisible
。