Adding Filter chips at Tenant.
This commit is contained in:
parent
411e3cae84
commit
def5ef71f6
@ -30,8 +30,8 @@ const TaskReportFilterPanel = forwardRef(({ handleFilter, setFilterdata, clearFi
|
|||||||
buildingIds: TaskReportDefaultValue.buildingIds || [],
|
buildingIds: TaskReportDefaultValue.buildingIds || [],
|
||||||
floorIds: TaskReportDefaultValue.floorIds || [],
|
floorIds: TaskReportDefaultValue.floorIds || [],
|
||||||
activityIds: TaskReportDefaultValue.activityIds || [],
|
activityIds: TaskReportDefaultValue.activityIds || [],
|
||||||
dateFrom: TaskReportDefaultValue.startDate,
|
dateFrom: TaskReportDefaultValue.dateFrom,
|
||||||
dateTo: TaskReportDefaultValue.endDate,
|
dateTo: TaskReportDefaultValue.dateTo,
|
||||||
};
|
};
|
||||||
}, [selectedProject]);
|
}, [selectedProject]);
|
||||||
|
|
||||||
|
|||||||
@ -203,7 +203,6 @@ const TaskReportList = ({ filter, filterData, removeFilterChip, clearFilter }) =
|
|||||||
removeFilterChip={removeFilterChip}
|
removeFilterChip={removeFilterChip}
|
||||||
clearFilter={clearFilter}
|
clearFilter={clearFilter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 table-responsive text-nowrap">
|
<div className="mt-2 table-responsive text-nowrap">
|
||||||
<table className="table">
|
<table className="table">
|
||||||
|
|||||||
100
src/components/Tenant/TenantFilterChips.jsx
Normal file
100
src/components/Tenant/TenantFilterChips.jsx
Normal 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;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
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 { FormProvider, useForm, useFormContext } from "react-hook-form";
|
||||||
import { defaultFilterValues, filterSchema } from "./TenantSchema";
|
import { defaultFilterValues, filterSchema } from "./TenantSchema";
|
||||||
import Label from "../common/Label";
|
import Label from "../common/Label";
|
||||||
@ -10,16 +10,44 @@ import { DateRangePicker1 } from "../common/DateRangePicker";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
const TenantFilterPanel = ({ onApply }) => {
|
const TenantFilterPanel = forwardRef(({ onApply, setFilterdata, clearFilter }, ref) => {
|
||||||
const [resetKey, setResetKey] = useState(0);
|
const [resetKey, setResetKey] = useState(0);
|
||||||
|
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
resolver: zodResolver(filterSchema),
|
resolver: zodResolver(filterSchema),
|
||||||
defaultValues: defaultFilterValues,
|
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(() => {
|
const handleClosePanel = useCallback(() => {
|
||||||
document.querySelector(".offcanvas.show .btn-close")?.click();
|
document.querySelector(".offcanvas.show .btn-close")?.click();
|
||||||
@ -32,9 +60,8 @@ const TenantFilterPanel = ({ onApply }) => {
|
|||||||
startDate: moment.utc(formData.startDate, "DD-MM-YYYY").toISOString(),
|
startDate: moment.utc(formData.startDate, "DD-MM-YYYY").toISOString(),
|
||||||
endDate: moment.utc(formData.endDate, "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"
|
endField="endDate"
|
||||||
resetSignal={resetKey}
|
resetSignal={resetKey}
|
||||||
defaultRange={false}
|
defaultRange={false}
|
||||||
|
className="w-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-strat mb-2">
|
<div className="text-strat mb-2">
|
||||||
@ -119,6 +147,6 @@ const TenantFilterPanel = ({ onApply }) => {
|
|||||||
</form>
|
</form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default TenantFilterPanel;
|
export default TenantFilterPanel;
|
||||||
|
|||||||
@ -7,12 +7,14 @@ import Pagination from "../common/Pagination";
|
|||||||
import { TenantTableSkeleton } from "./TenanatSkeleton";
|
import { TenantTableSkeleton } from "./TenanatSkeleton";
|
||||||
import { useTenantContext } from "../../pages/Tenant/TenantPage";
|
import { useTenantContext } from "../../pages/Tenant/TenantPage";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import TenantFilterChips from "./TenantFilterChips";
|
||||||
|
|
||||||
const TenantsList = ({
|
const TenantsList = ({
|
||||||
filters,
|
filters,
|
||||||
searchText,
|
searchText,
|
||||||
setIsRefetching,
|
setIsRefetching,
|
||||||
setRefetchFn,
|
setRefetchFn,
|
||||||
|
filterData, removeFilterChip, clearFilter
|
||||||
}) => {
|
}) => {
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -135,54 +137,65 @@ const TenantsList = ({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className=" mt-3">
|
<div className="main-content">
|
||||||
<div className=" text-nowrap table-responsive">
|
<div className="col-12 mb-2 mt-2 px-4">
|
||||||
<table className="table border-top dataTable text-nowrap">
|
<TenantFilterChips
|
||||||
<thead>
|
filter={filters}
|
||||||
<tr className="shadow-sm">
|
filterData={filterData}
|
||||||
{TenantColumns.map((col) => (
|
removeFilterChip={removeFilterChip}
|
||||||
<th key={col.key} className="sorting d-table-cell">
|
clearFilter={clearFilter}
|
||||||
<div className={col.align}>{col.label}</div>
|
/>
|
||||||
</th>
|
|
||||||
))}
|
</div>
|
||||||
</tr>
|
<div className=" mt-3">
|
||||||
</thead>
|
<div className=" text-nowrap table-responsive">
|
||||||
<tbody>
|
<table className="table border-top dataTable text-nowrap">
|
||||||
{data?.data.length > 0 ? (
|
<thead>
|
||||||
data.data.map((tenant) => (
|
<tr className="shadow-sm">
|
||||||
<tr key={tenant.id} style={{ height: "50px" }}>
|
{TenantColumns.map((col) => (
|
||||||
{TenantColumns.map((col) => (
|
<th key={col.key} className="sorting d-table-cell">
|
||||||
<td
|
<div className={col.align}>{col.label}</div>
|
||||||
key={col.key}
|
</th>
|
||||||
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>
|
</tr>
|
||||||
)}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{data?.data.length > 0 ? (
|
||||||
{data?.data?.length > 0 && (
|
data.data.map((tenant) => (
|
||||||
<Pagination
|
<tr key={tenant.id} style={{ height: "50px" }}>
|
||||||
currentPage={currentPage}
|
{TenantColumns.map((col) => (
|
||||||
totalPages={data.totalPages}
|
<td
|
||||||
onPageChange={paginate}
|
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>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import React, {
|
|||||||
useContext,
|
useContext,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useRef,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@ -59,6 +60,7 @@ const TenantPage = () => {
|
|||||||
const [refetchFn, setRefetchFn] = useState(null);
|
const [refetchFn, setRefetchFn] = useState(null);
|
||||||
const [filters, setFilters] = useState();
|
const [filters, setFilters] = useState();
|
||||||
|
|
||||||
|
|
||||||
// ---------- Hooks ----------
|
// ---------- Hooks ----------
|
||||||
const debouncedSearch = useDebounce(searchText, 500);
|
const debouncedSearch = useDebounce(searchText, 500);
|
||||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||||
@ -66,6 +68,9 @@ const TenantPage = () => {
|
|||||||
const isSuperTenant = useHasUserPermission(SUPPER_TENANT);
|
const isSuperTenant = useHasUserPermission(SUPPER_TENANT);
|
||||||
const canManageTenants = useHasUserPermission(MANAGE_TENANTS);
|
const canManageTenants = useHasUserPermission(MANAGE_TENANTS);
|
||||||
const isSelfTenant = useHasUserPermission(VIEW_TENANTS);
|
const isSelfTenant = useHasUserPermission(VIEW_TENANTS);
|
||||||
|
const updatedRef = useRef();
|
||||||
|
const [filterData, setFilterdata] = useState(null);
|
||||||
|
const [filter, setFilter] = useState('')
|
||||||
|
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
resolver: zodResolver(filterSchema),
|
resolver: zodResolver(filterSchema),
|
||||||
@ -77,8 +82,18 @@ const TenantPage = () => {
|
|||||||
setFilters(values);
|
setFilters(values);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const clearFilter = () => {
|
||||||
|
setFilters(defaultFilterValues); // update active filters
|
||||||
|
updatedRef.current?.resetFields?.(); // reset RHF fields in panel
|
||||||
|
};
|
||||||
|
|
||||||
const filterPanelElement = useMemo(
|
const filterPanelElement = useMemo(
|
||||||
() => <TenantFilterPanel onApply={handleApplyFilters} />,
|
() => <TenantFilterPanel
|
||||||
|
onApply={handleApplyFilters}
|
||||||
|
ref={updatedRef}
|
||||||
|
clearFilter={clearFilter}
|
||||||
|
setFilterdata={setFilterdata}
|
||||||
|
/>,
|
||||||
[handleApplyFilters]
|
[handleApplyFilters]
|
||||||
);
|
);
|
||||||
// ---------- Fab Filter Panel ----------
|
// ---------- Fab Filter Panel ----------
|
||||||
@ -112,6 +127,20 @@ const TenantPage = () => {
|
|||||||
// ---------- Context Value ----------
|
// ---------- Context Value ----------
|
||||||
const contextValue = {};
|
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 (
|
return (
|
||||||
<TenantContext.Provider value={contextValue}>
|
<TenantContext.Provider value={contextValue}>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
@ -121,7 +150,7 @@ const TenantPage = () => {
|
|||||||
{ label: "Tenant", link: null },
|
{ 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 */}
|
{/* Super Tenant Actions */}
|
||||||
{isSuperTenant && (
|
{isSuperTenant && (
|
||||||
<div className="p-0">
|
<div className="p-0">
|
||||||
@ -157,10 +186,13 @@ const TenantPage = () => {
|
|||||||
{/* Tenant List or Access Denied */}
|
{/* Tenant List or Access Denied */}
|
||||||
{isSuperTenant ? (
|
{isSuperTenant ? (
|
||||||
<TenantsList
|
<TenantsList
|
||||||
|
setRefetchFn={setRefetchFn}
|
||||||
|
setIsRefetching={setIsRefetching}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
searchText={debouncedSearch}
|
searchText={debouncedSearch}
|
||||||
setIsRefetching={setIsRefetching}
|
filterData={filterData}
|
||||||
setRefetchFn={setRefetchFn}
|
removeFilterChip={handleRemoveChip}
|
||||||
|
clearFilter={clearFilter}
|
||||||
/>
|
/>
|
||||||
) : !isSelfTenant ? (
|
) : !isSelfTenant ? (
|
||||||
<div className="text-center my-4 p-2">
|
<div className="text-center my-4 p-2">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user