Searching Records with Form

Searching records using form

Below example shows a sample implementation of a search form to filter a list of records.

Step 1

Import the Form from antd

import { Form } from "antd";

Step 2

Initialize a local form variable

// search form
const [form] = Form.useForm();

Getting field values

const formValues = form.getFieldsValue();

Full example

Below example shows how a form with a search (keyword) input and a toggle switch is used to filter set of templates.

/* eslint-disable jsx-a11y/click-events-have-key-events */
import React, { useEffect, useState, useRef } from "react";
import { connect } from "dva";
import PropTypes from "prop-types";
import InfiniteScroll from "react-infinite-scroller";
import { Input, Button, Switch, Form, Modal, message } from "antd";
import ZCPLoader from "@/components/ZCPLoader";
import SingleTaskViewDrawer from "@/pages/Party/Components/TaskView/SingleTaskViewDrawer";
import DocumentTitle from "react-document-title";
import Page from "@/components/Page";
import Breadcrumbs from "@/components/Breadcrumbs";
import moment from "moment";
import { isAdmin } from "@/components/common/access/RoleAuthorization";
import CheckValidation from "@/components/CheckValidation";
import { Clock } from "react-bootstrap-icons";
 
import CreateTaskFromTemplate from "./CreateTaskFromTemplate";
import styles from "./index.less";
import NewTemplateDrawer from "./NewTemplateDrawer";
import ViewTaskListItem from "../Tasks/components/ViewTaskListItem";
 
/**
 *@Templates - Renders the list of public/shared task templates.
 Allows owners/admins of the templates to delete task templates.
 */
const { Search } = Input;
 
const Templates = (props) => {
  const { templates, currentUser, loadingTemplates, deletingTemplate } = props;
  const [listWidth, setListWidth] = useState("100%");
  const [createTaskModalVisible, setCreateTaskModalVisible] = useState(false);
  const [templateId, setTemplateId] = useState(null);
  const [taskDrawer, setTaskDrawer] = useState(false);
  const [selectedTask, setSelectedTask] = useState(null);
  const [filterByTemplateOwnerId, setFilterByTemplateOwnerId] = useState("");
  const [searchText, setSearchText] = useState("");
  const [
    showTemplateDeleteConfirmationPopup,
    setShowTemplateDeleteConfirmationPopup,
  ] = useState(false);
 
  // search form
  const [form] = Form.useForm();
 
  // infinite scroll
  const viewSize = 50;
  const [currentPage, setCurrentPage] = useState(0);
  const [hasMore, setHasMore] = useState(false);
  const [openNewTemplateDrawer, setOpenNewTemplateDrawer] = useState(false);
  const parentRef = useRef(null);
 
  const calculateStartIndex = (page) => (page + 1) * viewSize - viewSize;
 
  const getAllTemplates = () => {
    setCurrentPage(0);
 
    const searchTemplatesFilteringCriteria = {
      taskcategoryIds: [],
      keyword: searchText || "",
      startIndex: 0,
      viewSize,
    };
 
    // get form values
    const formValues = form.getFieldsValue();
    searchTemplatesFilteringCriteria.keyword = formValues.keyword;
    let ownerIdToFilterBy = "";
    if (formValues.mineOnly) {
      ownerIdToFilterBy = currentUser.id;
    }
 
    searchTemplatesFilteringCriteria.ownerId = ownerIdToFilterBy;
 
    props.dispatch({
      type: "template/getTemplate",
      payload: searchTemplatesFilteringCriteria,
      cb: () => {
        setHasMore(true);
      },
    });
  };
 
  function debounce(func, wait) {
    let timeout;
    return (...args) => {
      const context = this;
      if (timeout) clearTimeout(timeout);
      timeout = setTimeout(() => {
        timeout = null;
        func.apply(context, args);
      }, wait);
    };
  }
 
  const debounceSearch = React.useCallback(debounce(getAllTemplates, 400), []);
 
  const deleteTemplate = (taskId) => {
    props.dispatch({
      type: "template/deleteTemplate",
      payload: {
        taskId,
      },
      cb: (res) => {
        if (res) {
          // hide the popup and refresh templates list
          setShowTemplateDeleteConfirmationPopup(false);
          message.success(`Template ${taskId} deleted successfully!`);
          getAllTemplates();
        }
      },
    });
  };
 
  /**
   * Returns true if the logged in user is the creator of the task to allow template deletion.
   * Also retuns true if logged in user has admin role.
   * @param {*} taskCreatorId
   */
  const canDeleteTemplate = (taskCreatorId) => {
    if (isAdmin()) {
      return true;
    }
 
    // not an admin check if the logged in user is the creator of the template.
    if (currentUser.id === taskCreatorId) {
      return true;
    }
 
    return false;
  };
  useEffect(() => {
    getAllTemplates();
  }, [filterByTemplateOwnerId]);
 
  const loadMoreHandler = (text) => {
    if (hasMore) {
      setCurrentPage((prevPage) => prevPage + 1);
      props.dispatch({
        type: "template/loadMoreTemplate",
        payload: {
          ownerId: filterByTemplateOwnerId || "",
          taskcategoryIds: [],
          keyword: text || "",
          startIndex: calculateStartIndex(currentPage + 1),
          viewSize,
        },
        cb: (res) => {
          if (res.length === 0) {
            message.info("No more data to fetch...");
            setHasMore(false);
            return;
          }
          if (page) {
            message.success("Data Fetched Successfully");
          }
          setHasMore(true);
        },
      });
    }
  };
 
  const createTemplate = () => {
    setOpenNewTemplateDrawer(true);
  };
 
  const pagePrimaryAction = () => (
    <div>
      <Button type="primary" id="create-workspace-btn" onClick={createTemplate}>
        Create new template
      </Button>
    </div>
  );
 
  return (
    <DocumentTitle title="Task templates">
      <div className="max-w-screen-lg mx-auto">
        <Page
          breadcrumbs={
            <Breadcrumbs
              path={[
                {
                  name: "Home",
                  path: "/dashboard",
                },
                {
                  name: "Task templates",
                },
              ]}
            />
          }
          title="Task templates"
          PrevNextNeeded="N"
          subTitle={<p>Manage your task templates here.</p>}
          primaryAction={pagePrimaryAction()}
        >
          <div className={styles.templateListCard}>
            <div>
              {/* Search form */}
              <Form
                form={form}
                layout="horizontal"
                initialValues={{ mineOnly: false, ownerId: "" }}
                // onValuesChange={onFormLayoutChange}
              >
                <div className="px-4 py-2 bg-white flex justify-between items-center mb-4 rounded shadow">
                  <div style={{ minWidth: "15vw" }}>
                    <div className="rounded-full border hover:bg-gray-100 px-2">
                      <Form.Item name="keyword">
                        <Input
                          allowClear
                          bordered={false}
                          onChange={(e) => {
                            debounceSearch(e.target.value, 0);
                          }}
                          size="large"
                          placeholder="Search templates"
                        />
                      </Form.Item>
                    </div>
                  </div>
                  <div
                    className="text-right"
                    title="See only your task templates, click to toggle."
                  >
                    <div className="app-label">Mine only?</div>
                    <Form.Item name="mineOnly">
                      <Switch onChange={getAllTemplates} />
                    </Form.Item>
                  </div>
                </div>
              </Form>
              <div
                className="flex flex-row justify-between"
                style={{ width: "100%" }}
              >
                <div
                  ref={parentRef}
                  className="shadow bg-white"
                  style={{
                    width: listWidth,
                    height: "calc((100vh - 5px) - 190px)",
                    overflow: "auto",
                  }}
                >
                  <ZCPLoader loading={loadingTemplates}>
                    {/* Infinite Scroller */}
                    <InfiniteScroll
                      loadMore={loadMoreHandler}
                      hasMore={!loadingTemplates && hasMore}
                      initialLoad={false}
                      useWindow={false}
                      getScrollParent={() => parentRef.current}
                    >
                      <div className="divide-y divide-gray-200">
                        {templates &&
                          templates?.records?.map((item) => (
                            <ViewTaskListItem
                              onClick={() => {
                                setTaskDrawer(true);
                                setSelectedTask(item);
                              }}
                              showTaskCategory
                              overlayClassName="transition-none flex space-x-2 items-center px-4 py-2 bg-white hover:bg-gray-100"
                              isTemplate
                              key={item.id}
                              task={item}
                              extraContent={
                                <div className="flex space-x-4">
                                  <div className="text-sm font-medium text-gray-600">
                                    #{item.id}
                                  </div>
                                  <div className="text-sm font-medium text-gray-600">
                                    <Clock className="mr-1" />{" "}
                                    {moment(item.createdDate).fromNow(true)} old
                                  </div>
                                  <Button
                                    onClick={(e) => {
                                      e.stopPropagation();
                                      setCreateTaskModalVisible(true);
                                      setTemplateId(item.id);
                                    }}
                                    size="small"
                                    type="default"
                                  >
                                    Use template
                                  </Button>
 
                                  <CheckValidation
                                    show={canDeleteTemplate(item?.creator?.id)}
                                  >
                                    <Button
                                      danger
                                      onClick={(e) => {
                                        setSelectedTask(item);
                                        setShowTemplateDeleteConfirmationPopup(
                                          true
                                        );
                                        e.stopPropagation();
                                      }}
                                      size="small"
                                      type="text"
                                    >
                                      Delete
                                    </Button>
                                  </CheckValidation>
                                </div>
                              }
                            />
                          ))}
                        {/* Add Spinner Here */}
                        {loadingTemplates && hasMore && (
                          <div className="p-2">
                            <ZCPLoader />
                          </div>
                        )}
                      </div>
                    </InfiniteScroll>
                  </ZCPLoader>
                </div>
                <SingleTaskViewDrawer
                  taskId={selectedTask?.id}
                  visible={taskDrawer}
                  setVisible={setTaskDrawer}
                  refreshTasks={getAllTemplates}
                />
              </div>
            </div>
          </div>
        </Page>
 
        <CheckValidation show={createTaskModalVisible}>
          <CreateTaskFromTemplate
            templateId={templateId}
            title="Create task from template"
            visible={createTaskModalVisible}
            setVisible={setCreateTaskModalVisible}
          />
        </CheckValidation>
        <SingleTaskViewDrawer
          taskId={selectedTask?.id}
          visible={taskDrawer}
          setVisible={setTaskDrawer}
        />
        {openNewTemplateDrawer && (
          <NewTemplateDrawer
            getAllTemplates={getAllTemplates}
            visible={openNewTemplateDrawer}
            setVisible={setOpenNewTemplateDrawer}
          />
        )}
        <Modal
          wrapClassName="app-modal-flat"
          closable={false}
          title={null}
          destroyOnClose
          footer={null}
          visible={showTemplateDeleteConfirmationPopup}
          maskClosable={false}
        >
          <div className="rounded-lg">
            <div className="p-8">
              <div className="text-xl font-semibold mb-4">
                Delete template &quot;{selectedTask?.name}&quot;?
              </div>
              <div className="font-medium text-gray-600">
                Are you sure you want to delete this template? Once deleted this
                action can not be undone and this template will not be available
                for creating tasks.
              </div>
            </div>
            <div className="px-8 py-4 rounded-b-lg flex space-x-2 justify-end bg-gray-100">
              <div>
                <Button
                  type="text"
                  size="large"
                  onClick={() => {
                    setShowTemplateDeleteConfirmationPopup(false);
                  }}
                >
                  Cancel
                </Button>
              </div>
              <div>
                <Button
                  type="primary"
                  size="large"
                  danger
                  loading={deletingTemplate}
                  onClick={() => {
                    deleteTemplate(selectedTask?.id);
                  }}
                >
                  {deletingTemplate ? "Deleting..." : "Delete"}
                </Button>
              </div>
            </div>
          </div>
        </Modal>
      </div>
    </DocumentTitle>
  );
};
 
/**
 * @mapStateToProps This is the function to fetch states from the redux
 */
const mapStateToProps = (state) => ({
  templates: state.template.templates,
  currentUser: state.user.currentUser,
  loadingTemplates: state.loading.effects["template/getTemplate"],
  deletingTemplate: state.loading.effects["template/deleteTemplate"],
});
 
Templates.propTypes = {
  /**
   * @templates paramType {array} - This is the list of templates.
   */
  templates: PropTypes.array.isRequired,
};
export default connect(mapStateToProps, (dispatch) => ({ dispatch }))(
  Templates
);