Advertisement
devraselmiah

Infinite

Jul 14th, 2024
35
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 11.92 KB | Source Code | 0 0
  1. "use client";
  2. import { useInfiniteQuery, useQueryClient } from "@tanstack/react-query";
  3. import React, { useRef, useState } from "react";
  4. import { RiArrowDropDownLine } from "react-icons/ri";
  5. import { RxCross2 } from "react-icons/rx";
  6. import { useInView } from "react-intersection-observer";
  7. import Skeleton from "react-loading-skeleton";
  8.  
  9. import KlassyImage from "@/components/Admin/common/KlassyImage";
  10. import UseAxiosClient from "@/hooks/UseAxiosClient";
  11. import useDebounce from "@/hooks/UseDebounce";
  12. import { prefixImagePath } from "@/utils/imagePathFinder";
  13.  
  14. const OutsideClickHandler = ({ children, onOutsideClick, className }) => {
  15.   const containerRef = useRef();
  16.  
  17.   React.useEffect(() => {
  18.     const handleOutsideClick = (event) => {
  19.       if (
  20.         containerRef.current &&
  21.         !containerRef.current.contains(event.target)
  22.       ) {
  23.         onOutsideClick();
  24.       }
  25.     };
  26.  
  27.     document.addEventListener("click", handleOutsideClick);
  28.  
  29.     return () => {
  30.       document.removeEventListener("click", handleOutsideClick);
  31.     };
  32.   }, [onOutsideClick]);
  33.  
  34.   return (
  35.     <div className={className} ref={containerRef}>
  36.       {children}
  37.     </div>
  38.   );
  39. };
  40.  
  41. const InfiniteInput = ({
  42.   totalSize = 15,
  43.   queryName,
  44.   apiPrefix,
  45.   selectedValue,
  46.   apiResKeys,
  47.   accessor = ["id"],
  48.   accessorVariation = ["id"],
  49.   placeholder,
  50.   inputClass = "",
  51.   width = "w-4/5",
  52.   height = "max-h-[250px]",
  53.   searchShow,
  54.   debounceTime = 300,
  55.   skeletonHeight = 25,
  56.   isMulti,
  57.   showImage = false,
  58.   maxMulti = Infinity,
  59.   isShowSelectedValue = isMulti,
  60.   zIndex = 10,
  61.   showValue = true,
  62.   defaultValue,
  63.   exchangeOrder = false,
  64.   searchKey = "name",
  65.   defaultMulti = [],
  66.   onRemoveFn,
  67. }) => {
  68.   const url = UseAxiosClient();
  69.   const [page, setPage] = useState(1);
  70.   const [search, setSearch] = useState("");
  71.   const [openInput, setOpenInput] = useState(false);
  72.   const [selectedName, setSelectedName] = useState("");
  73.   const [onValuePress, setOnValuePress] = useState(false);
  74.   const [multiValue, setMultiValue] = useState(defaultMulti);
  75.   const [selectedItems, setSelectedItems] = useState([]);
  76.   const { ref, inView } = useInView();
  77.   const queryClient = useQueryClient();
  78.   const { debouncedSearch, debounceLoading } = useDebounce(
  79.     search,
  80.     debounceTime
  81.   );
  82.  
  83.   const { data, fetchNextPage } = useInfiniteQuery({
  84.     queryKey: [queryName, debouncedSearch],
  85.  
  86.     queryFn: async ({ pageParam = 1 }) => {
  87.       //  increase the page dynamically
  88.       const res = await url.get(apiPrefix, {
  89.         params: {
  90.           [searchKey]: debouncedSearch,
  91.           pages: pageParam,
  92.           pageSize: totalSize,
  93.         },
  94.       });
  95.       return res.data;
  96.     },
  97.     getNextPageParam: (lastPage, pages) => {
  98.       return lastPage.hasMore ? pages.length + 1 : undefined;
  99.     },
  100.     getPreviousPageParam: (firstPage) => {
  101.       return firstPage.hasMore ? firstPage : undefined;
  102.     },
  103.     initialPageParam: 1,
  104.     refetchOnWindowFocus: false,
  105.   });
  106.  
  107.   React.useEffect(() => {
  108.     if (inView && page <= data?.pages[0]["totalPages"]) {
  109.       fetchNextPage();
  110.       setPage((prev) => prev + 1);
  111.     }
  112.   }, [fetchNextPage, inView]);
  113.  
  114.   React.useEffect(() => {
  115.     setPage(1);
  116.     queryClient.resetQueries({ queryKey: [queryName, debouncedSearch] });
  117.   }, [search, debouncedSearch]);
  118.  
  119.   const handleRemove = () => {
  120.     setSelectedName("");
  121.     setSearch("");
  122.     selectedValue(null);
  123.     setMultiValue([]);
  124.     setOpenInput(false);
  125.   };
  126.  
  127.   React.useEffect(() => {
  128.     if (isMulti) {
  129.       selectedValue(multiValue);
  130.     }
  131.   }, [multiValue]);
  132.  
  133.   const handleButtonClick = (item, isVariation) => {
  134.     setSelectedName(
  135.       !isVariation
  136.         ? exchangeOrder
  137.           ? item.offline_customer_id
  138.             ? item.offline_customers.name
  139.             : item.users.name
  140.           : item[searchShow]
  141.         : `${item?.products?.name} -- ${item?.attributes?.name}:${item?.attribute_values?.name}`
  142.     );
  143.     setOpenInput(false);
  144.     setOnValuePress(true);
  145.     if (!isVariation) {
  146.       let accessorValues = {};
  147.       [...new Set([...accessor, "id"])].forEach((acc) => {
  148.         accessorValues[acc] = item[acc];
  149.       });
  150.       setMultiValue((prev) => [...prev, accessorValues]);
  151.       if (!isMulti) {
  152.         selectedValue(accessorValues);
  153.       }
  154.       setSelectedItems((prev) => [...prev, item]);
  155.     } else if (isVariation) {
  156.       let accessorValues = {};
  157.       [...new Set([...accessorVariation, "id"])].forEach((acc) => {
  158.         accessorValues[acc] = item[acc];
  159.       });
  160.       selectedValue({ ...accessorValues, name: selectedName });
  161.       isMulti && setMultiValue((prev) => [...prev, accessorValues]);
  162.       setSelectedName(null);
  163.       setSelectedItems((prev) => [...prev, item]);
  164.     }
  165.   };
  166.  
  167.   const handleMultiRemove = (removeValueId) => {
  168.     setMultiValue(multiValue.filter((item) => item.id !== removeValueId));
  169.     setSelectedItems(selectedItems.filter((item) => item.id !== removeValueId));
  170.     if (onRemoveFn) {
  171.       const removedItem = Object.assign(
  172.         {},
  173.         ...multiValue.filter((item) => item.id === removeValueId)
  174.       );
  175.  
  176.       onRemoveFn(removedItem?.id);
  177.     }
  178.   };
  179.  
  180.   const mergedItems = selectedItems.concat(
  181.     data?.pages.flatMap((page) => page[apiResKeys] || []).filter((item) => !selectedItems.some((selected) => selected.id === item.id))
  182.   );
  183.  
  184.   const CloseProps = (
  185.     <button
  186.       onClick={(e) => {
  187.         e.preventDefault();
  188.         handleRemove();
  189.       }}
  190.       className="p-2 bg-white dark:bg-inherit"
  191.       style={{
  192.         position: "absolute",
  193.         right: "1px",
  194.         top: "20px",
  195.         transform: "translateY(-50%)",
  196.       }}
  197.     >
  198.       {selectedName === "" ? (
  199.         <RiArrowDropDownLine />
  200.       ) : (
  201.         <RxCross2 className="text-red-500" />
  202.       )}
  203.     </button>
  204.   );
  205.  
  206.   return (
  207.     <OutsideClickHandler
  208.       className={`z-${zIndex} m-auto relative ${width}`}
  209.       onOutsideClick={() => setOpenInput(false)}
  210.     >
  211.       <input
  212.         type="text"
  213.         value={
  214.           defaultValue
  215.             ? defaultValue
  216.             : onValuePress && showValue
  217.               ? selectedName
  218.               : search
  219.         }
  220.         onChange={(e) => setSearch(e.target.value)}
  221.         onFocusCapture={() => setOpenInput(true)}
  222.         onKeyDown={() => setOnValuePress(false)}
  223.         placeholder={placeholder}
  224.         className={`${inputClass} border border-gray-300 dark:bg-slate-700 p-2 rounded-md w-full placeholder:text-[15px]`}
  225.       />
  226.       {showValue && CloseProps}
  227.       {openInput && (
  228.         <div>
  229.           <div
  230.             style={{ position: "absolute" }}
  231.             className={`w-full text-sm border-l-2 border-r-2 ${
  232.               mergedItems.length !== 0 && "border-b-2"
  233.             } bg-white dark:bg-slate-700 rounded-md  scroll-m-0 scrollbar-thumb-gray-800 scrollbar-track-black overflow-y-auto ${height}`}
  234.           >
  235.             {debounceLoading ? (
  236.               <Skeleton count={totalSize} height={skeletonHeight} />
  237.             ) : mergedItems.length !== 0 ? (
  238.               mergedItems.map((item, index) =>
  239.                 item?.is_variant !== 1 ? (
  240.                   <button
  241.                     className={`text-left ${
  242.                       showImage
  243.                         ? "grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 justify-start align-middle items-center"
  244.                         : ""
  245.                     }  w-full border-b-[1px] text-gray-500 dark:text-gray-100 cursor-pointer p-2 border-b-gray-300 disabled:text-black-bold disabled:bg-gray-200 mb-1 disabled:cursor-not-allowed`}
  246.                     ref={ref}
  247.                     key={index}
  248.                     onClick={(e) => {
  249.                       e.preventDefault();
  250.                       handleButtonClick(item);
  251.                       // setSelectedName(item[searchShow]);
  252.                     }}
  253.                     disabled={
  254.                       isMulti && multiValue.length >= maxMulti
  255.                         ? true
  256.                         : isMulti
  257.                           ? multiValue.find(
  258.                               (multiVal) => multiVal.id === item.id
  259.                             )
  260.                           : false
  261.                     }
  262.                   >
  263.                     {showImage && (
  264.                       <KlassyImage
  265.                         src={prefixImagePath(item["thumbnail_img"])}
  266.                         className="col-span-1"
  267.                       />
  268.                     )}
  269.                     <p className="col-span-3 sm:col-span-4 md:col-span-5">
  270.                       {exchangeOrder
  271.                         ? item.offline_customer_id
  272.                           ? item.offline_customers.name
  273.                           : item.users.name
  274.                         : item[searchShow]}
  275.                     </p>
  276.                   </button>
  277.                 ) : (
  278.                   item?.product_variations.map((variation) => (
  279.                     <button
  280.                       className={`text-left ${
  281.                         showImage
  282.                           ? "grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 justify-start align-middle items-center"
  283.                           : ""
  284.                       }  w-full border-b-[1px] text-gray-500 dark:text-gray-100 cursor-pointer p-2 border-b-gray-300 disabled:text-black-bold disabled:bg-gray-200 mb-1 disabled:cursor-not-allowed`}
  285.                       ref={ref}
  286.                       key={variation.id}
  287.                       onClick={(e) => {
  288.                         e.preventDefault();
  289.                         handleButtonClick(variation, true);
  290.                         // setSelectedName(item[searchShow]);
  291.                       }}
  292.                     >
  293.                       {showImage && (
  294.                         <KlassyImage
  295.                           src={prefixImagePath(
  296.                             variation?.products?.thumbnail_img
  297.                           )}
  298.                           className="col-span-1"
  299.                         />
  300.                       )}
  301.                       <p className="col-span-3 sm:col-span-4 md:col-span-5">
  302.                         {`${variation?.products?.name} -- ${variation?.attributes?.name}:
  303.                   ${variation?.attribute_values?.name}`}
  304.                       </p>
  305.                     </button>
  306.                   ))
  307.                 )
  308.               )
  309.             ) : (
  310.               <div className="p-2 text-center">
  311.                 <h1 className="text-base text-gray-500">
  312.                   Not found any{" "}
  313.                   <u className="first-letter:capitalize">{searchShow}</u> of{" "}
  314.                   <u className="first-letter:capitalize">{apiResKeys}</u>.
  315.                 </h1>
  316.               </div>
  317.             )}
  318.           </div>
  319.         </div>
  320.       )}
  321.  
  322.       {multiValue.length !== 0 && isShowSelectedValue && (
  323.         <div className="border-[1px] mt-2 bg-[#F9F9F9] dark:bg-gray-800 p-2 mb-5 border-dashed rounded-sm border-[#B3B3B3]">
  324.           <div className="flex flex-wrap gap-2">
  325.             {multiValue.map((acc, index) => (
  326.               <button
  327.                 className="flex items-center justify-center p-1 bg-[#FFF0F0] rounded-sm w-fit dark:text-black"
  328.                 key={index}
  329.                 onClick={(e) => e.preventDefault()}
  330.               >
  331.                 <p className="text-sm">
  332.                   {exchangeOrder
  333.                     ? acc.offline_customer_id
  334.                       ? acc.offline_customers.name
  335.                       : acc.users.name
  336.                     : acc[searchShow]}
  337.                 </p>
  338.                 <RxCross2
  339.                   className="ml-1 text-base text-red-500 cursor-pointer"
  340.                   onClick={(e) => {
  341.                     e.preventDefault();
  342.                     handleMultiRemove(acc.id);
  343.                   }}
  344.                 />
  345.               </button>
  346.             ))}
  347.           </div>
  348.         </div>
  349.       )}
  350.     </OutsideClickHandler>
  351.   );
  352. };
  353.  
  354. export default InfiniteInput;
  355.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement