TheStruggleEzzReal
TheStruggleEzzReal

Reputation: 17

How to send filter & sort parameters from react.js to SpringBoot

I'm using React.js for frontend and Spring Boot for backend. I'm trying to implement sorting and filtering but I'm not sure if this is the best approach. My first uncertainty is how to construct an URL for REST API call which includes sort and filter options. And then if I'm handling it well on backend (but I haven't gone deep into that yet because I'm trying to figure out the pare I mentioned above).

I'm sharing my code

import axios from "axios";
import React, { useEffect, useState } from "react";
import Sidebar from "./Sidebar";
import { CiFilter } from "react-icons/ci";
import { IoIosArrowDropdown } from "react-icons/io";

const Requests = () => {
  const [requests, setRequests] = useState([]);
  const [url, setUrl] = useState(`http://localhost:8080/requests`);
  const [requestDetails, setRequestDetails] = useState(null);
  const [isRequestModalOpen, setIsRequestModalOpen] = useState(false); 
  const [isStatusDropdownOpen, setIsStatusDropdownOpen] = useState(false);
  const [isTimeRangeDropdownOpen, setIsTimeRangeDropdownOpen] = useState(false);

  /* varijable za razlicite filtere */
/*   const [sort, setSort] = useState("");
  const [order, setOrder] = useState(""); */
  const [status, setStatus] = useState("");
  const [sortField, setSortField] = useState("");
  const [sortDirection, setSortDirection] = useState("asc");

  const timeRangeList = [
    "Today",
    "Last 7 days",
    "Last month",
    "Last year",
    "All",
  ];

  const getRquests = async () => {
    const fetchedRequests = await axios.get(url + `?status=${status}&sortField=${sortField}&sortDirection=${sortDirection}`);
    setRequests(fetchedRequests.data);
  };
 
  useEffect(() => {
    getRquests();
  }, [url, status, sortDirection, sortField]);

  const toggleRequestModal = () => {
    setIsRequestModalOpen(!isRequestModalOpen);
  };

  const showRequestDetails = (request) => {
    toggleRequestModal();
    setRequestDetails(request);
  };
  
  const onClickApprove = async () => {
    try {
      const updatedRequest = { ...requestDetails, status: "APPROVED" };
      await axios.put(`${url}/${requestDetails.id}`, updatedRequest);
      setRequests(prevRequests =>
        prevRequests.map(req =>
          req.id === requestDetails.id ? { ...req, status: "APPROVED" } : req
        )
      );
      toggleRequestModal();
    } catch (error) {
        console.error("Error approving request:", error);
    }
  };

  const onClickDecline = async () => {
    try {
      const updatedRequest = { ...requestDetails, status: "DECLINED" };
      await axios.put(`${url}/${requestDetails.id}`, updatedRequest);
      setRequests(prevRequests =>
        prevRequests.map(req =>
          req.id === requestDetails.id ? { ...req, status: "DECLINED" } : req
        )
      );
      toggleRequestModal();
    } catch (error) {
        console.error("Error declining request:", error);
    }
  };

  // nac jednostavnije rjesenje ?
  const filterByTime = () => {
    setSortField("timeCreated");
    {sortDirection === "DESC" ? setSortDirection("ASC") : setSortDirection("DESC")};
  }; 

  const showStatusDropdown = () => {
    setIsStatusDropdownOpen(!isStatusDropdownOpen);
  };

  const handleSetStatus = (s) => {
    setStatus(s);
    showStatusDropdown();
  };

  const showTimeRangeDropdown = () => {
    setIsTimeRangeDropdownOpen(!isTimeRangeDropdownOpen);
  };

/*   const handleSetTimeRange = () => {
    setTimeRange();
  }
 */
  return(
    <>
      <Sidebar reqCount={requests.length}/>
      <div className="text-white grid justify-center items-center mt-20 sm:ml-60">

        <div className="relative flex justify-between mr-[70px] text-xs py-1 sm:mr-[75px] md:mr-[80px] 2xl:mr-[93px]">

          <button onClick={showTimeRangeDropdown} className="text-white bg-blue-700 hover:bg-blue-800 px-2 py-1 rounded-lg mb-2 lg:px-[10px] lg:py-[6px] 2xl:text-sm">
            <span className="flex justify-center items-center">
              Options
              <span className="ml-1"><IoIosArrowDropdown /></span>
            </span>
          </button>

          <button onClick={showStatusDropdown} className="text-white bg-blue-700 hover:bg-blue-800 px-2 py-1 rounded-lg mb-2 lg:px-[10px] lg:py-[6px] 2xl:text-sm">
            <span className="flex justify-center items-center">
              Status
              <span className="ml-1"><IoIosArrowDropdown /></span>
            </span>
          </button>

          {isTimeRangeDropdownOpen && (
            <div className="absolute bg-[#001E28] text-center rounded-lg py-2 w-24 mt-1 top-[34px] z-10 2xl:text-sm 2xl:py-3 2xl:w-44 2xl:top-11">
              {timeRangeList.map((tr) => {
                return(
                  <ul>
                    <li onClick={() => setTimeRange(tr)} className="px-3 py-1 hover:bg-[#002a39] cursor-pointer" >
                      {tr}
                    </li>
                  </ul>
                );
              })}
            </div>
          )}

          {isStatusDropdownOpen && (
            <div className="absolute bg-[#001E28] text-center rounded-lg py-2 w-24 mt-1 top-[34px] -right-2 z-10 2xl:text-sm 2xl:py-3 2xl:w-32 2xl:-right3 2xl:top-11">
              <ul>
                <li className="px-3 py-1 hover:bg-[#002a39] cursor-pointer"  onClick={() => handleSetStatus('PENDING')} >
                  PENDING
                </li>
                <li className="px-3 py-1 hover:bg-[#002a39] cursor-pointer" onClick={() => handleSetStatus('APPROVED')}>
                  APPROVED
                </li>
                <li className="px-3 py-1 hover:bg-[#002a39] cursor-pointer" onClick={() => handleSetStatus('DECLINED')}>
                  DECLINED
                </li>
                <li className="px-3 py-1 hover:bg-[#002a39] cursor-pointer" onClick={() => handleSetStatus('')}>
                  ALL
                </li>
              </ul>
            </div>
          )}
        </div>


        <table className="text-[11px]  md:text-[13.5px] lg:text-[14.5px] xl:text-[15.5px] 2xl:text-[16.5px]">
          <thead className="bg-[#001E28]">
            <tr>
              <th className="p-2">
                Requested by
              </th>
              <th className="p-2">
                <button onClick={filterByTime} type="button" className="flex justify-center items-center lg:ml-4 xl:ml-5">
                  Request time <span className="ml-3"><CiFilter /></span>
                </button>
              </th>
              <th className="p-2">
                Status 
              </th>
            </tr>
          </thead>
          <tbody>
            {requests.map((request) => {
              return(
                <tr key={request.id}>
                  <td className="p-1 lg:py-[6px] lg:px-[16px] 2xl:px-[20px]">
                    {request.requester.email}
                  </td>
                  <td className="p-1 lg:py-[6px] lg:px-[16px] 2xl:px-[20px]">
                    {request.timeCreated.slice(0, 10)} {request.timeCreated.slice(11, 19)}
                  </td>
                  <td className="p-1 lg:py-[6px] lg:px-[16px] 2xl:px-[20px]">
                    {request.status}
                  </td>
                  <td>
                    <span className={`h-[5px] w-[5px] rounded-2xl inline-block text-end mb-[1px] ml-[1px] ${request.status === 'APPROVED' ? 'bg-green-400' : request.status === 'DECLINED' ? 'bg-red-400' : 'bg-yellow-200'} lg:h-[6px] lg:w-[6px] 2xl:h-[7px] 2xl:w-[7px]`}></span>
                  </td>
                  <td>
                    <button
                      type="button"
                      className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-blue-300 font-medium rounded-lg text-[10px] px-[6px] py-[3px] me-2 mb-2 focus:outline-none ml-4 
                        md:text-[12px] lg:px-[8px] lg:py-[4px] 2xl:text-base
                      "
                      onClick={() => showRequestDetails(request)}
                      >
                      Open
                    </button>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>

        <div>
          {isRequestModalOpen && requestDetails && (
            <div className={`fixed inset-0 z-50 flex justify-center items-center bg-black bg-opacity-75 ${isRequestModalOpen ? '' : 'hidden'}`}>
              <div className="relative p-4 w-[95%] max-w-2xl max-h-full bg-gray-100 rounded-xl shadow-2xl shadow-gray-500">
                <div className="flex items-center justify-between p-4 border-b-2 rounded-t border-gray-300">
                  <h3 className="text-xl font-semibold text-gray-900">
                    Request details
                  </h3>
                  <button
                    onClick={toggleRequestModal}
                    className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-2"
                  >
                    <svg
                      className="w-3 h-3"
                      aria-hidden="true"
                      xmlns="http://www.w3.org/2000/svg"
                      fill="none"
                      viewBox="0 0 14 14"
                    >
                      <path
                        stroke="currentColor"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth="2"
                        d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
                      />
                    </svg>
                  </button>
                </div>
                <div>
                  <p className="text-black px-2 py-4">
                    {requestDetails.message}
                  </p>
                </div>
                <button onClick={onClickApprove} type="button" className="focus:outline-none text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2">
                  Approve
                </button>
                <button onClick={onClickDecline} type="button" className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 ">
                  Decline
                </button>
              </div>
            </div>
          )}
        </div>

      </div>


{/*       <div className="h-screen flex items-center">

        <table className="text-white text-xs">
          <thead>
            <tr>
              <th>
                Requested By
              </th>

              <th className="">
                Request Time              
              </th>

              <th>
                Status
              </th>
            </tr>
          </thead>
          <tbody>
            {requests.map((req, index) => {
             
              return(
                <tr key={index}>
                  <td>
                    <span className="text-white">{req.requester.email}</span>
                  </td>

                  <td>
                    {req.timeCreated.slice(0, 10)} {req.timeCreated.slice(11, 19)}
                  </td>

                  <td>
                    {req.status}
                  </td>
                  
                  <td>
                    <span className={`h-2 w-5 rounded-3xl inline-block ml-2 mb-[2px] ${req.status === 'APPROVED' ? 'bg-green-400' : req.status === 'DECLINED' ? 'bg-red-400' : 'bg-yellow-200'}`}></span>
                  </td>

                  <td>
                    <button
                      type="button"
                      className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-blue-300 font-medium rounded-lg text-sm px-3 py-2 me-2 mb-2 focus:outline-none ml-4"
                      onClick={() => showRequestDetails(req)}
                      >
                      Open
                    </button>
                  </td>
                </tr>
              );
              
            })}
          </tbody>
        </table>

        <div>  
          {isRequestModalOpen && requestDetails && (
            <div className={`fixed inset-0 z-50 flex justify-center items-center bg-black bg-opacity-50 ${isRequestModalOpen ? '' : 'hidden'}`}>
              <div className="relative p-4 w-full max-w-2xl max-h-full bg-white rounded-lg shadow-sm dark:bg-gray-700">
                <div className="flex items-center justify-between p-4 border-b rounded-t dark:border-gray-600 border-gray-200">
                  <h3 className="text-xl font-semibold text-gray-900 dark:text-white">
                    Request details
                  </h3>
                  <button
                    onClick={toggleRequestModal}
                    className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-2"
                  >
                    <svg
                      className="w-3 h-3"
                      aria-hidden="true"
                      xmlns="http://www.w3.org/2000/svg"
                      fill="none"
                      viewBox="0 0 14 14"
                    >
                      <path
                        stroke="currentColor"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                        strokeWidth="2"
                        d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
                      />
                    </svg>
                  </button>
                </div>
                <div>
                  <p>message</p>
                </div>
                <div>
                  <p className="text-black px-2 py-4">
                    {requestDetails.message}
                  </p>
                </div>
                <button onClick={onClickApprove} type="button" className="focus:outline-none text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2">
                  Approve
                </button>
                <button onClick={onClickDecline} type="button" className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 ">
                  Decline
                </button>
              </div>
            </div>
          )}
        </div>  
      </div> */}
    </>
  );
}
export default Requests;

As you can see when user clicks on the filter icon the filterByTime function is called and there I'm changing state of variables which are included in url for (what I think it is a good way) to construct an url dynamically. As I write this I also remembered that I will have to implement sorting by multiple columns in that case is the url going to be really large is that a bad practice?


And this is the backend code:
Controller

@GetMapping
public ResponseEntity<List<AdminRequestDto>> fetchAllRequests(
        @RequestParam(name = "status", defaultValue = "") String status,
        @RequestParam(name = "sortField", defaultValue = "id") String sortField,
        @RequestParam(name = "sortDirection", defaultValue = "ASC") String sortDirection
) {
    return new ResponseEntity<>(requestService.getAllRequests(status, sortField, sortDirection), HttpStatus.OK);
}


Service

public interface RequestService {
    Request createRequest(Request request);
    List<AdminRequestDto> getAllRequests(String status, String sortField, String sortDirection);
    UserRequestDto getRequestById(Long id);
    List<UserRequestDto> userGetAllRequestsByUserId(Long id);
    List<AdminRequestDto> adminGetAllRequestsByUserId(Long id);
    Request updateRequest(Long id, Request request);
}


ServiceImpl

public List<AdminRequestDto> getAllRequests(String status, String sortField, String sortDirection) {
    // filtriram po status za opciju na FE-u kada korisnik odabere filter
    Status enumStatus;
    List<Request> requests = new ArrayList<>();

    if(!status.isEmpty()) {
        enumStatus = Status.valueOf(status);
        if(sortDirection.equals("ASC")){
            requests = requestRepository.findByStatusOrderByTimeCreatedAsc(enumStatus);
        } else {
            requests = requestRepository.findByStatusOrderByTimeCreatedDesc(enumStatus);
        }

        List<AdminRequestDto> requestsDto = requests.stream()
                .map(request -> modelMapper.map(request, AdminRequestDto.class))
                .toList();
        return requestsDto;

    } else {
        if(sortDirection.equals("ASC")) {

            requests = requestRepository.findAll(Sort.by(sortField).ascending());
        } else {
            requests = requestRepository.findAll(Sort.by(sortField).descending());
        }

        List<AdminRequestDto> requestsDto = requests.stream()
                .map(request -> modelMapper.map(request, AdminRequestDto.class))
                .toList();

        return requestsDto;
    }
}

Upvotes: -1

Views: 18

Answers (0)

Related Questions