Adding Filter chips at Tenant.

This commit is contained in:
Kartik Sharma 2025-12-10 14:24:28 +05:30
parent 411e3cae84
commit def5ef71f6
6 changed files with 233 additions and 61 deletions

View File

@ -30,8 +30,8 @@ const TaskReportFilterPanel = forwardRef(({ handleFilter, setFilterdata, clearFi
buildingIds: TaskReportDefaultValue.buildingIds || [],
floorIds: TaskReportDefaultValue.floorIds || [],
activityIds: TaskReportDefaultValue.activityIds || [],
dateFrom: TaskReportDefaultValue.startDate,
dateTo: TaskReportDefaultValue.endDate,
dateFrom: TaskReportDefaultValue.dateFrom,
dateTo: TaskReportDefaultValue.dateTo,
};
}, [selectedProject]);

View File

@ -203,7 +203,6 @@ const TaskReportList = ({ filter, filterData, removeFilterChip, clearFilter }) =
removeFilterChip={removeFilterChip}
clearFilter={clearFilter}
/>
</div>
<div className="mt-2 table-responsive text-nowrap">
<table className="table">

View File

@ -0,0 +1,100 @@
import React, { useMemo } from "react";
import { TENANT_STATUS, reference } from "../../utils/constants";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
const TenantFilterChips = ({ filter, filterData, removeFilterChip, clearFilter }) => {
const data = filterData || {};
const filterChips = useMemo(() => {
const chips = [];
const addGroup = (ids, list, label, key, valueKey = "id") => {
if (!ids || ids.length === 0) return;
const items = ids.map((id) => {
const found = list?.find((i) => i[valueKey] === id);
return {
id,
name: found?.name || id,
};
});
chips.push({ key, label, items });
};
// Industries
addGroup(filter?.industryIds, data, "Industries", "industryIds");
// References
addGroup(filter?.references, reference, "References", "references", "val");
// Tenant Status
addGroup(filter?.tenantStatusIds, TENANT_STATUS, "Status", "tenantStatusIds");
// Date Range Chip
if (filter?.startDate || filter?.endDate) {
chips.push({
key: "date",
label: "Date Range",
items: [
{
id: "date-range",
name: `${filter?.startDate ? formatUTCToLocalTime(filter.startDate) : ""}${
filter?.endDate ? " to " + formatUTCToLocalTime(filter.endDate) : ""
}`,
},
],
});
}
return chips;
}, [filter, filterData]);
if (!filterChips.length) return null;
return (
<div className="d-flex flex-wrap align-items-center gap-2 mt-2">
{filterChips.map((chipGroup) => (
<div key={chipGroup.key} className="d-flex align-items-center flex-wrap">
<span className="fw-semibold me-2">{chipGroup.label}:</span>
{chipGroup.items.map((item) => (
<span
key={item.id}
className="d-flex align-items-center bg-light rounded px-2 py-1 me-1"
>
<span>{item.name}</span>
{/* Remove Date Range → remove full filter */}
{chipGroup.key === "date" ? (
<button
type="button"
className="btn-close btn-close-white btn-sm ms-2"
style={{
filter: "invert(1) grayscale(1)",
opacity: 0.7,
fontSize: "0.6rem",
}}
onClick={clearFilter}
/>
) : (
<button
type="button"
className="btn-close btn-close-white btn-sm ms-2"
style={{
filter: "invert(1) grayscale(1)",
opacity: 0.7,
fontSize: "0.6rem",
}}
onClick={() => removeFilterChip(chipGroup.key, item.id)}
/>
)}
</span>
))}
</div>
))}
</div>
);
};
export default TenantFilterChips;

View File

@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import React, { useState, useCallback, useEffect } from "react";
import React, { useState, useCallback, useEffect, useImperativeHandle, forwardRef, useMemo } from "react";
import { FormProvider, useForm, useFormContext } from "react-hook-form";
import { defaultFilterValues, filterSchema } from "./TenantSchema";
import Label from "../common/Label";
@ -10,16 +10,44 @@ import { DateRangePicker1 } from "../common/DateRangePicker";
import moment from "moment";
import { useLocation } from "react-router-dom";
const TenantFilterPanel = ({ onApply }) => {
const TenantFilterPanel = forwardRef(({ onApply, setFilterdata, clearFilter }, ref) => {
const [resetKey, setResetKey] = useState(0);
const methods = useForm({
resolver: zodResolver(filterSchema),
defaultValues: defaultFilterValues,
});
const { handleSubmit, reset, setValue } = methods;
const { data: industries, isLoading } = useIndustries();
const dynamicDefaultFilter = useMemo(() => {
return {
...defaultFilterValues,
industryIds: defaultFilterValues.industryIds || [],
tenantStatusIds: defaultFilterValues.tenantStatusIds || [],
references: defaultFilterValues.references || [],
startDate: defaultFilterValues.startDate,
endDate: defaultFilterValues.endDate,
};
}, [defaultFilterValues]);
useImperativeHandle(ref, () => ({
resetFieldValue: (name, value) => {
setValue(name, value);
},
resetFields: () => {
reset(defaultFilterValues);
setResetKey(prev => prev + 1); // reset date picker
}
}));
useEffect(() => {
if (industries && setFilterdata) {
setFilterdata(industries);
}
}, [industries, setFilterdata]);
const { handleSubmit, reset } = methods;
const { data: industries = [], isLoading } = useIndustries();
const handleClosePanel = useCallback(() => {
document.querySelector(".offcanvas.show .btn-close")?.click();
@ -32,9 +60,8 @@ const TenantFilterPanel = ({ onApply }) => {
startDate: moment.utc(formData.startDate, "DD-MM-YYYY").toISOString(),
endDate: moment.utc(formData.endDate, "DD-MM-YYYY").toISOString(),
});
handleClosePanel();
},
[onApply, handleClosePanel]
[onApply]
);
@ -65,6 +92,7 @@ const TenantFilterPanel = ({ onApply }) => {
endField="endDate"
resetSignal={resetKey}
defaultRange={false}
className="w-100"
/>
</div>
<div className="text-strat mb-2">
@ -119,6 +147,6 @@ const TenantFilterPanel = ({ onApply }) => {
</form>
</FormProvider>
);
};
});
export default TenantFilterPanel;

View File

@ -7,12 +7,14 @@ import Pagination from "../common/Pagination";
import { TenantTableSkeleton } from "./TenanatSkeleton";
import { useTenantContext } from "../../pages/Tenant/TenantPage";
import { useNavigate } from "react-router-dom";
import TenantFilterChips from "./TenantFilterChips";
const TenantsList = ({
filters,
searchText,
setIsRefetching,
setRefetchFn,
filterData, removeFilterChip, clearFilter
}) => {
const [currentPage, setCurrentPage] = useState(1);
const navigate = useNavigate();
@ -135,54 +137,65 @@ const TenantsList = ({
);
return (
<>
<div className=" mt-3">
<div className=" text-nowrap table-responsive">
<table className="table border-top dataTable text-nowrap">
<thead>
<tr className="shadow-sm">
{TenantColumns.map((col) => (
<th key={col.key} className="sorting d-table-cell">
<div className={col.align}>{col.label}</div>
</th>
))}
</tr>
</thead>
<tbody>
{data?.data.length > 0 ? (
data.data.map((tenant) => (
<tr key={tenant.id} style={{ height: "50px" }}>
{TenantColumns.map((col) => (
<td
key={col.key}
className={`d-table-cell px-3 py-2 align-middle ${col.align ?? ""
}`}
>
{col.customRender
? col.customRender(tenant)
: col.getValue(tenant)}
</td>
))}
</tr>
))
) : (
<tr>
<td
colSpan={TenantColumns.length + 1}
className="text-center py-4 border-0"
>
No Tenants Found
</td>
<div className="main-content">
<div className="col-12 mb-2 mt-2 px-4">
<TenantFilterChips
filter={filters}
filterData={filterData}
removeFilterChip={removeFilterChip}
clearFilter={clearFilter}
/>
</div>
<div className=" mt-3">
<div className=" text-nowrap table-responsive">
<table className="table border-top dataTable text-nowrap">
<thead>
<tr className="shadow-sm">
{TenantColumns.map((col) => (
<th key={col.key} className="sorting d-table-cell">
<div className={col.align}>{col.label}</div>
</th>
))}
</tr>
)}
</tbody>
</table>
{data?.data?.length > 0 && (
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
)}
</thead>
<tbody>
{data?.data.length > 0 ? (
data.data.map((tenant) => (
<tr key={tenant.id} style={{ height: "50px" }}>
{TenantColumns.map((col) => (
<td
key={col.key}
className={`d-table-cell px-3 py-2 align-middle ${col.align ?? ""
}`}
>
{col.customRender
? col.customRender(tenant)
: col.getValue(tenant)}
</td>
))}
</tr>
))
) : (
<tr>
<td
colSpan={TenantColumns.length + 1}
className="text-center py-4 border-0"
>
No Tenants Found
</td>
</tr>
)}
</tbody>
</table>
{data?.data?.length > 0 && (
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
)}
</div>
</div>
</div>
</>

View File

@ -5,6 +5,7 @@ import React, {
useContext,
useCallback,
useMemo,
useRef,
} from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
@ -59,6 +60,7 @@ const TenantPage = () => {
const [refetchFn, setRefetchFn] = useState(null);
const [filters, setFilters] = useState();
// ---------- Hooks ----------
const debouncedSearch = useDebounce(searchText, 500);
const { setOffcanvasContent, setShowTrigger } = useFab();
@ -66,6 +68,9 @@ const TenantPage = () => {
const isSuperTenant = useHasUserPermission(SUPPER_TENANT);
const canManageTenants = useHasUserPermission(MANAGE_TENANTS);
const isSelfTenant = useHasUserPermission(VIEW_TENANTS);
const updatedRef = useRef();
const [filterData, setFilterdata] = useState(null);
const [filter, setFilter] = useState('')
const methods = useForm({
resolver: zodResolver(filterSchema),
@ -77,8 +82,18 @@ const TenantPage = () => {
setFilters(values);
}, []);
const clearFilter = () => {
setFilters(defaultFilterValues); // update active filters
updatedRef.current?.resetFields?.(); // reset RHF fields in panel
};
const filterPanelElement = useMemo(
() => <TenantFilterPanel onApply={handleApplyFilters} />,
() => <TenantFilterPanel
onApply={handleApplyFilters}
ref={updatedRef}
clearFilter={clearFilter}
setFilterdata={setFilterdata}
/>,
[handleApplyFilters]
);
// ---------- Fab Filter Panel ----------
@ -112,6 +127,20 @@ const TenantPage = () => {
// ---------- Context Value ----------
const contextValue = {};
const handleRemoveChip = (key, id) => {
setFilters((prev) => {
const updated = { ...prev };
if (Array.isArray(updated[key])) {
updated[key] = updated[key].filter((v) => v !== id);
} else {
updated[key] = null;
}
setTimeout(() => updatedRef.current?.resetFieldValue(key, updated[key]), 0);
return updated;
});
};
return (
<TenantContext.Provider value={contextValue}>
<div className="container-fluid">
@ -121,7 +150,7 @@ const TenantPage = () => {
{ label: "Tenant", link: null },
]}
/>
<div className="card text-center my-4 p-md-5 px-1 py-3 pb-10">
<div className="card page-min-h text-center my-4 p-md-5 px-1 py-3 pb-10">
{/* Super Tenant Actions */}
{isSuperTenant && (
<div className="p-0">
@ -157,10 +186,13 @@ const TenantPage = () => {
{/* Tenant List or Access Denied */}
{isSuperTenant ? (
<TenantsList
setRefetchFn={setRefetchFn}
setIsRefetching={setIsRefetching}
filters={filters}
searchText={debouncedSearch}
setIsRefetching={setIsRefetching}
setRefetchFn={setRefetchFn}
filterData={filterData}
removeFilterChip={handleRemoveChip}
clearFilter={clearFilter}
/>
) : !isSelfTenant ? (
<div className="text-center my-4 p-2">