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 "{selectedTask?.name}"?
</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
);